工业级部署指南:在西门子IOT2050(Debian 12)上搭建.NET 9.0环境与应用部署(进阶篇)

作者:dephixf日期:2025/11/13

在工业物联网(IIoT)场景中,实时监控设备状态和能源消耗是提升生产效率的核心需求。本文将详细介绍如何在 IOT2050 设备(搭载 Debian 12 系统)上,完成两大监控系统的部署:基于 Nginx 的设备监控管理 HTML 静态页面(负责可视化展示设备状态、工单数据)和**Asp.net Core 能源监控系统**(负责后端数据处理、能源趋势分析),实现从设备状态到能源消耗的全维度监控。

一、环境准备:IOT2050 基础配置

核心前提

  • IOT2050 设备已安装 Debian 12 操作系统(64 位)
  • 设备已联网,可通过 SSH 远程连接(推荐使用 Putty 或 Xshell)
  • 本地开发环境:VS 2022(用于Asp.net Core 项目发布)、浏览器(用于测试访问)

第一步:安装 Nginx 服务器

Nginx 是轻量级高性能 Web 服务器,专为静态资源(HTML/CSS/JS)优化,是部署前端页面的首选。

  • 更新系统软件包:
1sudo apt update 2
  • 安装 Nginx:
1sudo apt install -y nginx 2
  • 验证 Nginx 状态(显示active (running)即为正常):
1sudo systemctl status nginx 2
  • 若未启动,执行启动命令:sudo systemctl start nginx

二、部署设备监控管理 HTML 页面

页面核心功能

该 HTML 页面基于 Tailwind CSS + Chart.js 开发,包含设备统计概览、状态分布图表、待处理工单、保养计划等模块,支持响应式布局,适配 IOT2050 本地显示和远程访问。

1<!DOCTYPE html> 2<html lang="zh-CN"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>设备管理系统看板</title> 7 <!-- Tailwind CSS --> 8 <script src="https://cdn.tailwindcss.com"></script> 9 <!-- Font Awesome --> 10 <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet"> 11 <!-- Chart.js --> 12 <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script> 13 14 <!-- Tailwind配置 --> 15 <script> 16 tailwind.config = { 17 theme: { 18 extend: { 19 colors: { 20 primary: '#165DFF', 21 secondary: '#0FC6C2', 22 success: '#00B42A', 23 warning: '#FF7D00', 24 danger: '#F53F3F', 25 info: '#86909C', 26 dark: '#1D2129', 27 light: '#F2F3F5' 28 }, 29 fontFamily: { 30 inter: ['Inter', 'system-ui', 'sans-serif'], 31 }, 32 animation: { 33 'fade-in': 'fadeIn 0.5s ease-in-out', 34 'slide-up': 'slideUp 0.5s ease-out', 35 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', 36 }, 37 keyframes: { 38 fadeIn: { 39 '0%': { opacity: '0' }, 40 '100%': { opacity: '1' }, 41 }, 42 slideUp: { 43 '0%': { transform: 'translateY(20px)', opacity: '0' }, 44 '100%': { transform: 'translateY(0)', opacity: '1' }, 45 } 46 } 47 }, 48 } 49 } 50 </script> 51 52 <!-- 自定义工具类 --> 53 <style type="text/tailwindcss"> 54 @layer utilities { 55 .content-auto { 56 content-visibility: auto; 57 } 58 .scrollbar-hide { 59 -ms-overflow-style: none; 60 scrollbar-width: none; 61 } 62 .scrollbar-hide::-webkit-scrollbar { 63 display: none; 64 } 65 .card-shadow { 66 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); 67 } 68 .hover-scale { 69 transition: transform 0.2s ease; 70 } 71 .hover-scale:hover { 72 transform: scale(1.02); 73 } 74 .gradient-bg { 75 background: linear-gradient(135deg, #165DFF 0%, #0FC6C2 100%); 76 } 77 } 78 </style> 79 80 <style> 81 /* 基础样式 */ 82 body { 83 font-family: 'Inter', system-ui, sans-serif; 84 overflow-x: hidden; 85 } 86 87 /* 平滑滚动 */ 88 html { 89 scroll-behavior: smooth; 90 } 91 92 /* 表格样式优化 */ 93 .data-table th { 94 font-weight: 600; 95 text-transform: uppercase; 96 font-size: 0.75rem; 97 letter-spacing: 0.05em; 98 } 99 100 /* 进度条动画 */ 101 .progress-bar { 102 transition: width 1s ease-in-out; 103 } 104 105 /* 卡片悬停效果 */ 106 .stat-card { 107 transition: all 0.3s ease; 108 } 109 .stat-card:hover { 110 box-shadow: 0 10px 30px rgba(22, 93, 255, 0.15); 111 transform: translateY(-5px); 112 } 113 </style> 114</head> 115<body class="bg-light min-h-screen"> 116 <!-- 顶部导航 --> 117 <header class="bg-white shadow-md fixed w-full top-0 z-50 transition-all duration-300" id="main-header"> 118 <div class="container mx-auto px-4"> 119 <div class="flex justify-between items-center py-4"> 120 <!-- 左侧Logo --> 121 <div class="flex items-center space-x-2"> 122 <div class="gradient-bg text-white p-2 rounded-lg"> 123 <i class="fa fa-cogs text-xl"></i> 124 </div> 125 <h1 class="text-xl font-bold text-dark">设备管理系统</h1> 126 </div> 127 128 <!-- 中间搜索 --> 129 <div class="hidden md:block flex-1 max-w-md mx-8"> 130 <div class="relative"> 131 <input type="text" placeholder="搜索设备、工单或人员..." 132 class="w-full py-2 pl-10 pr-4 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"> 133 <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i> 134 </div> 135 </div> 136 137 <!-- 右侧工具栏 --> 138 <div class="flex items-center space-x-4"> 139 <!-- 通知 --> 140 <button class="relative p-2 rounded-full hover:bg-gray-100 transition-colors"> 141 <i class="fa fa-bell text-gray-600"></i> 142 <span class="absolute top-0 right-0 w-2 h-2 bg-danger rounded-full"></span> 143 </button> 144 145 <!-- 设置 --> 146 <button class="p-2 rounded-full hover:bg-gray-100 transition-colors"> 147 <i class="fa fa-cog text-gray-600"></i> 148 </button> 149 150 <!-- 用户 --> 151 <div class="flex items-center space-x-2 cursor-pointer group"> 152 <img src="https://picsum.photos/id/1005/200/200" alt="用户头像" class="w-8 h-8 rounded-full object-cover border-2 border-transparent group-hover:border-primary transition-all"> 153 <span class="hidden md:inline text-sm font-medium text-gray-700">管理员</span> 154 <i class="fa fa-angle-down text-gray-500 group-hover:text-primary transition-colors"></i> 155 </div> 156 </div> 157 </div> 158 </div> 159 </header> 160 161 <!-- 主内容区域 --> 162 <main class="container mx-auto px-4 pt-24 pb-12"> 163 <!-- 页面标题和统计概览 --> 164 <div class="mb-8"> 165 <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"> 166 <div> 167 <h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-dark">设备管理看板</h2> 168 <p class="text-gray-500 mt-1">实时监控设备运行状态、效率和维护情况</p> 169 </div> 170 <div class="flex space-x-3 mt-4 md:mt-0"> 171 <button class="px-4 py-2 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-50 transition-colors flex items-center"> 172 <i class="fa fa-download mr-2 text-gray-600"></i> 173 <span>导出报告</span> 174 </button> 175 <button class="px-4 py-2 bg-primary text-white rounded-lg shadow-sm hover:bg-primary/90 transition-colors flex items-center"> 176 <i class="fa fa-refresh mr-2"></i> 177 <span>刷新数据</span> 178 </button> 179 </div> 180 </div> 181 182 <!-- 状态卡片 --> 183 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> 184 <!-- 设备总数 --> 185 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 186 <div class="flex justify-between items-start"> 187 <div> 188 <p class="text-gray-500 text-sm">设备总数</p> 189 <h3 class="text-3xl font-bold mt-1" id="total-equipment">0</h3> 190 <div class="flex items-center mt-2 text-success text-sm"> 191 <i class="fa fa-arrow-up mr-1"></i> 192 <span>2台 (本周)</span> 193 </div> 194 </div> 195 <div class="bg-primary/10 p-3 rounded-lg"> 196 <i class="fa fa-machine text-primary text-xl"></i> 197 </div> 198 </div> 199 </div> 200 201 <!-- 运行中设备 --> 202 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 203 <div class="flex justify-between items-start"> 204 <div> 205 <p class="text-gray-500 text-sm">运行中设备</p> 206 <h3 class="text-3xl font-bold mt-1" id="running-equipment">0</h3> 207 <div class="flex items-center mt-2 text-success text-sm"> 208 <i class="fa fa-arrow-up mr-1"></i> 209 <span>87%</span> 210 </div> 211 </div> 212 <div class="bg-success/10 p-3 rounded-lg"> 213 <i class="fa fa-play text-success text-xl"></i> 214 </div> 215 </div> 216 </div> 217 218 <!-- 待维修设备 --> 219 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 220 <div class="flex justify-between items-start"> 221 <div> 222 <p class="text-gray-500 text-sm">待维修设备</p> 223 <h3 class="text-3xl font-bold mt-1" id="maintenance-equipment">0</h3> 224 <div class="flex items-center mt-2 text-danger text-sm"> 225 <i class="fa fa-arrow-up mr-1"></i> 226 <span>2台 (较上周)</span> 227 </div> 228 </div> 229 <div class="bg-warning/10 p-3 rounded-lg"> 230 <i class="fa fa-wrench text-warning text-xl"></i> 231 </div> 232 </div> 233 </div> 234 235 <!-- 平均OEE --> 236 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 237 <div class="flex justify-between items-start"> 238 <div> 239 <p class="text-gray-500 text-sm">平均OEE</p> 240 <h3 class="text-3xl font-bold mt-1" id="average-oee">0%</h3> 241 <div class="flex items-center mt-2 text-success text-sm"> 242 <i class="fa fa-arrow-up mr-1"></i> 243 <span>3.2% (较上月)</span> 244 </div> 245 </div> 246 <div class="bg-secondary/10 p-3 rounded-lg"> 247 <i class="fa fa-line-chart text-secondary text-xl"></i> 248 </div> 249 </div> 250 </div> 251 </div> 252 </div> 253 254 <!-- 主要图表区域 --> 255 <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> 256 <!-- 设备状态分布 --> 257 <div class="lg:col-span-1 bg-white rounded-xl p-6 card-shadow"> 258 <div class="flex justify-between items-center mb-4"> 259 <h3 class="font-bold text-lg text-dark">设备状态分布</h3> 260 <div class="flex space-x-2"> 261 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 262 </div> 263 </div> 264 <div class="h-64 flex justify-center items-center"> 265 <canvas id="equipment-status-chart"></canvas> 266 </div> 267 <div class="grid grid-cols-2 gap-2 mt-4"> 268 <div class="flex items-center space-x-2"> 269 <span class="w-3 h-3 rounded-full bg-success"></span> 270 <span class="text-sm text-gray-600">运行中</span> 271 </div> 272 <div class="flex items-center space-x-2"> 273 <span class="w-3 h-3 rounded-full bg-warning"></span> 274 <span class="text-sm text-gray-600">待维修</span> 275 </div> 276 <div class="flex items-center space-x-2"> 277 <span class="w-3 h-3 rounded-full bg-danger"></span> 278 <span class="text-sm text-gray-600">故障中</span> 279 </div> 280 <div class="flex items-center space-x-2"> 281 <span class="w-3 h-3 rounded-full bg-info"></span> 282 <span class="text-sm text-gray-600">闲置中</span> 283 </div> 284 </div> 285 </div> 286 287 <!-- OEE趋势图 --> 288 <div class="lg:col-span-2 bg-white rounded-xl p-6 card-shadow"> 289 <div class="flex justify-between items-center mb-4"> 290 <h3 class="font-bold text-lg text-dark">OEE趋势 (近30天)</h3> 291 <div class="flex space-x-2"> 292 <button class="px-2 py-1 text-xs rounded-md bg-primary/10 text-primary"></button> 293 <button class="px-2 py-1 text-xs rounded-md hover:bg-gray-100"></button> 294 <button class="px-2 py-1 text-xs rounded-md hover:bg-gray-100"></button> 295 <button class="p-1 rounded hover:bg-gray-100 ml-2"><i class="fa fa-download text-gray-500"></i></button> 296 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 297 </div> 298 </div> 299 <div class="h-64"> 300 <canvas id="oee-trend-chart"></canvas> 301 </div> 302 </div> 303 </div> 304 305 <!-- 工单和计划区域 --> 306 <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> 307 <!-- 待处理工单 --> 308 <div class="bg-white rounded-xl p-6 card-shadow"> 309 <div class="flex justify-between items-center mb-4"> 310 <h3 class="font-bold text-lg text-dark">待处理工单</h3> 311 <div class="flex space-x-2"> 312 <select class="text-sm border-none bg-transparent focus:outline-none focus:ring-0 text-gray-500"> 313 <option>全部</option> 314 <option>维修工单</option> 315 <option>保养工单</option> 316 </select> 317 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 318 </div> 319 </div> 320 <div class="overflow-x-auto"> 321 <table class="data-table w-full"> 322 <thead> 323 <tr class="border-b border-gray-100"> 324 <th class="py-3 px-4 text-left text-gray-500">工单编号</th> 325 <th class="py-3 px-4 text-left text-gray-500">设备名称</th> 326 <th class="py-3 px-4 text-left text-gray-500">类型</th> 327 <th class="py-3 px-4 text-left text-gray-500">优先级</th> 328 <th class="py-3 px-4 text-left text-gray-500">截止日期</th> 329 </tr> 330 </thead> 331 <tbody id="pending-workorders"> 332 <!-- 动态生成 --> 333 </tbody> 334 </table> 335 </div> 336 <div class="mt-4 text-center"> 337 <button class="text-primary hover:text-primary/80 text-sm font-medium">查看全部工单 <i class="fa fa-angle-right ml-1"></i></button> 338 </div> 339 </div> 340 341 <!-- 今日保养计划 --> 342 <div class="bg-white rounded-xl p-6 card-shadow"> 343 <div class="flex justify-between items-center mb-4"> 344 <h3 class="font-bold text-lg text-dark">今日保养计划</h3> 345 <div class="flex space-x-2"> 346 <button class="px-3 py-1 text-sm rounded-md bg-primary/10 text-primary flex items-center"> 347 <i class="fa fa-plus mr-1"></i> 添加计划 348 </button> 349 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 350 </div> 351 </div> 352 <div class="space-y-4" id="maintenance-schedule"> 353 <!-- 动态生成 --> 354 </div> 355 <div class="mt-4 text-center"> 356 <button class="text-primary hover:text-primary/80 text-sm font-medium">查看全部计划 <i class="fa fa-angle-right ml-1"></i></button> 357 </div> 358 </div> 359 </div> 360 361 <!-- 人员工作负荷区域 --> 362 <div class="bg-white rounded-xl p-6 card-shadow mb-8"> 363 <div class="flex justify-between items-center mb-4"> 364 <h3 class="font-bold text-lg text-dark">保养人员工作负荷</h3> 365 <div class="flex space-x-2"> 366 <button class="px-2 py-1 text-xs rounded-md bg-primary text-white">本周</button> 367 <button class="px-2 py-1 text-xs rounded-md hover:bg-gray-100">本月</button> 368 <button class="p-1 rounded hover:bg-gray-100 ml-2"><i class="fa fa-download text-gray-500"></i></button> 369 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 370 </div> 371 </div> 372 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> 373 <!-- 人员负荷卡片将动态生成 --> 374 <div id="personnel-load-container"></div> 375 </div> 376 </div> 377 378 <!-- 设备详情列表 --> 379 <div class="bg-white rounded-xl p-6 card-shadow"> 380 <div class="flex justify-between items-center mb-6"> 381 <h3 class="font-bold text-lg text-dark">设备运行详情</h3> 382 <div class="flex items-center space-x-4"> 383 <div class="relative"> 384 <input type="text" placeholder="搜索设备..." 385 class="py-2 pl-10 pr-4 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm"> 386 <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"></i> 387 </div> 388 <select class="text-sm border border-gray-200 rounded-lg py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary bg-white"> 389 <option>全部状态</option> 390 <option>运行中</option> 391 <option>待维修</option> 392 <option>故障中</option> 393 <option>闲置中</option> 394 </select> 395 </div> 396 </div> 397 <div class="overflow-x-auto"> 398 <table class="data-table w-full"> 399 <thead> 400 <tr class="border-b border-gray-100"> 401 <th class="py-3 px-4 text-left text-gray-500">设备编号</th> 402 <th class="py-3 px-4 text-left text-gray-500">设备名称</th> 403 <th class="py-3 px-4 text-left text-gray-500">位置</th> 404 <th class="py-3 px-4 text-left text-gray-500">状态</th> 405 <th class="py-3 px-4 text-left text-gray-500">OEE</th> 406 <th class="py-3 px-4 text-left text-gray-500">运行时长</th> 407 <th class="py-3 px-4 text-left text-gray-500">操作</th> 408 </tr> 409 </thead> 410 <tbody id="equipment-details"> 411 <!-- 动态生成 --> 412 </tbody> 413 </table> 414 </div> 415 <div class="mt-6 flex justify-between items-center"> 416 <div class="text-sm text-gray-500">显示 1-10 项,共 <span id="total-equipment-count">0</span></div> 417 <div class="flex space-x-1"> 418 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 text-gray-400 hover:border-primary hover:text-primary transition-colors"> 419 <i class="fa fa-angle-left"></i> 420 </button> 421 <button class="w-8 h-8 flex items-center justify-center rounded-md bg-primary text-white">1</button> 422 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 hover:border-primary hover:text-primary transition-colors">2</button> 423 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 hover:border-primary hover:text-primary transition-colors">3</button> 424 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 hover:border-primary hover:text-primary transition-colors"> 425 <i class="fa fa-angle-right"></i> 426 </button> 427 </div> 428 </div> 429 </div> 430 </main> 431 432 <!-- 页脚 --> 433 <footer class="bg-white border-t border-gray-200 py-4"> 434 <div class="container mx-auto px-4"> 435 <div class="flex flex-col md:flex-row justify-between items-center"> 436 <div class="text-sm text-gray-500 mb-4 md:mb-0">© 2023 设备管理系统. 保留所有权利.</div> 437 <div class="flex space-x-4"> 438 <a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">使用帮助</a> 439 <a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">隐私政策</a> 440 <a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">联系我们</a> 441 </div> 442 </div> 443 </div> 444 </footer> 445 446 <!-- 脚本 --> 447 <script> 448 // 模拟数据 449 const mockData = { 450 // 设备统计数据 451 stats: { 452 total: 56, 453 running: 49, 454 maintenance: 3, 455 fault: 2, 456 idle: 2, 457 averageOee: 87.3 458 }, 459 460 // OEE趋势数据 (近30天) 461 oeeTrend: Array.from({length: 30}, (_, i) => ({ 462 date: [`6月${i+1}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.i.md), 463 oee: 75 + Math.random() * 20 464 })), 465 466 // 待处理工单 467 pendingWorkorders: [ 468 { id: 'WO-20230615-001', equipment: '注塑机-001', type: '维修', priority: '高', deadline: '2023-06-16' }, 469 { id: 'WO-20230615-002', equipment: '冲压机-003', type: '保养', priority: '中', deadline: '2023-06-17' }, 470 { id: 'WO-20230615-003', equipment: '包装机-005', type: '维修', priority: '中', deadline: '2023-06-18' }, 471 { id: 'WO-20230615-004', equipment: '输送带-002', type: '保养', priority: '低', deadline: '2023-06-19' }, 472 { id: 'WO-20230615-005', equipment: '焊接机-007', type: '维修', priority: '高', deadline: '2023-06-16' } 473 ], 474 475 // 今日保养计划 476 maintenanceSchedule: [ 477 { id: 'MS-20230615-001', equipment: '注塑机-001', time: '09:00', person: '张师傅', status: '已完成' }, 478 { id: 'MS-20230615-002', equipment: '冲压机-003', time: '11:30', person: '李师傅', status: '进行中' }, 479 { id: 'MS-20230615-003', equipment: '包装机-005', time: '14:00', person: '王师傅', status: '待开始' }, 480 { id: 'MS-20230615-004', equipment: '输送带-002', time: '16:30', person: '赵师傅', status: '待开始' } 481 ], 482 483 // 保养人员工作负荷 484 personnelLoad: [ 485 { name: '张师傅', completed: 8, pending: 3, capacity: 15 }, 486 { name: '李师傅', completed: 6, pending: 5, capacity: 12 }, 487 { name: '王师傅', completed: 4, pending: 2, capacity: 10 }, 488 { name: '赵师傅', completed: 5, pending: 4, capacity: 12 } 489 ], 490 491 // 设备运行详情 492 equipmentDetails: [ 493 { id: 'EQ-001', name: '注塑机-001', location: 'A车间-01', status: '运行中', oee: 92.5, runtime: '234h' }, 494 { id: 'EQ-002', name: '冲压机-001', location: 'B车间-02', status: '运行中', oee: 88.3, runtime: '198h' }, 495 { id: 'EQ-003', name: '冲压机-002', location: 'B车间-03', status: '故障中', oee: 0, runtime: '0h' }, 496 { id: 'EQ-004', name: '包装机-001', location: 'C车间-01', status: '运行中', oee: 90.1, runtime: '215h' }, 497 { id: 'EQ-005', name: '包装机-002', location: 'C车间-02', status: '待维修', oee: 76.5, runtime: '189h' }, 498 { id: 'EQ-006', name: '输送带-001', location: 'D车间-01', status: '运行中', oee: 85.7, runtime: '240h' }, 499 { id: 'EQ-007', name: '焊接机-001', location: 'E车间-01', status: '运行中', oee: 89.2, runtime: '176h' }, 500 { id: 'EQ-008', name: '焊接机-002', location: 'E车间-02', status: '闲置中', oee: 0, runtime: '0h' }, 501 { id: 'EQ-009', name: '车床-001', location: 'F车间-01', status: '运行中', oee: 91.8, runtime: '203h' }, 502 { id: 'EQ-010', name: '钻床-001', location: 'F车间-02', status: '待维修', oee: 78.3, runtime: '165h' } 503 ] 504 }; 505 506 // 更新统计数据 507 function updateStats() { 508 const { stats } = mockData; 509 document.getElementById('total-equipment').textContent = stats.total; 510 document.getElementById('running-equipment').textContent = stats.running; 511 document.getElementById('maintenance-equipment').textContent = stats.maintenance; 512 document.getElementById('average-oee').textContent = `${stats.averageOee}%`; 513 document.getElementById('total-equipment-count').textContent = mockData.equipmentDetails.length; 514 } 515 516 // 渲染设备状态图表 517 function renderEquipmentStatusChart() { 518 const { stats } = mockData; 519 const ctx = document.getElementById('equipment-status-chart').getContext('2d'); 520 521 new Chart(ctx, { 522 type: 'doughnut', 523 data: { 524 labels: ['运行中', '待维修', '故障中', '闲置中'], 525 datasets: [{ 526 data: [stats.running, stats.maintenance, stats.fault, stats.idle], 527 backgroundColor: ['#00B42A', '#FF7D00', '#F53F3F', '#86909C'], 528 borderWidth: 0, 529 hoverOffset: 5 530 }] 531 }, 532 options: { 533 responsive: true, 534 maintainAspectRatio: false, 535 plugins: { 536 legend: { 537 display: false 538 }, 539 tooltip: { 540 callbacks: { 541 label: function(context) { 542 const label = context.label || ''; 543 const value = context.raw || 0; 544 const total = mockData.stats.total; 545 const percentage = Math.round((value / total) * 100); 546 return [`${label}: ${value}台 (${percentage}%)`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.label.md); 547 } 548 } 549 } 550 }, 551 cutout: '70%' 552 } 553 }); 554 } 555 556 // 渲染OEE趋势图表 557 function renderOeeTrendChart() { 558 const { oeeTrend } = mockData; 559 const ctx = document.getElementById('oee-trend-chart').getContext('2d'); 560 561 new Chart(ctx, { 562 type: 'line', 563 data: { 564 labels: oeeTrend.map(item => item.date), 565 datasets: [{ 566 label: 'OEE值', 567 data: oeeTrend.map(item => item.oee), 568 borderColor: '#165DFF', 569 backgroundColor: 'rgba(22, 93, 255, 0.1)', 570 borderWidth: 2, 571 tension: 0.3, 572 fill: true, 573 pointRadius: 0, 574 pointHoverRadius: 4, 575 pointBackgroundColor: '#165DFF', 576 pointHoverBackgroundColor: '#ffffff', 577 pointHoverBorderColor: '#165DFF', 578 pointHoverBorderWidth: 2 579 }] 580 }, 581 options: { 582 responsive: true, 583 maintainAspectRatio: false, 584 interaction: { 585 mode: 'index', 586 intersect: false, 587 }, 588 plugins: { 589 legend: { 590 display: false 591 }, 592 tooltip: { 593 backgroundColor: 'rgba(255, 255, 255, 0.95)', 594 titleColor: '#1D2129', 595 bodyColor: '#86909C', 596 borderColor: 'rgba(0, 0, 0, 0.05)', 597 borderWidth: 1, 598 padding: 10, 599 boxPadding: 5, 600 usePointStyle: true, 601 callbacks: { 602 label: function(context) { 603 return `OEE: ${context.raw.toFixed(1)}%`; 604 } 605 } 606 } 607 }, 608 scales: { 609 x: { 610 grid: { 611 display: false 612 }, 613 ticks: { 614 maxRotation: 0, 615 autoSkip: true, 616 maxTicksLimit: 10, 617 color: '#86909C', 618 font: { 619 size: 10 620 } 621 } 622 }, 623 y: { 624 beginAtZero: false, 625 min: 70, 626 grid: { 627 color: 'rgba(0, 0, 0, 0.03)' 628 }, 629 ticks: { 630 color: '#86909C', 631 font: { 632 size: 10 633 }, 634 callback: function(value) { 635 return value + '%'; 636 } 637 } 638 } 639 } 640 } 641 }); 642 } 643 644 // 渲染待处理工单 645 function renderPendingWorkorders() { 646 const container = document.getElementById('pending-workorders'); 647 const { pendingWorkorders } = mockData; 648 649 container.innerHTML = ''; 650 651 pendingWorkorders.forEach(order => { 652 const row = document.createElement('tr'); 653 row.className = 'border-b border-gray-50 hover:bg-gray-50 transition-colors'; 654 655 // 优先级样式 656 let priorityClass = ''; 657 let priorityText = ''; 658 659 switch(order.priority) { 660 case '高': 661 priorityClass = 'bg-danger/10 text-danger'; 662 priorityText = '高'; 663 break; 664 case '中': 665 priorityClass = 'bg-warning/10 text-warning'; 666 priorityText = '中'; 667 break; 668 case '低': 669 priorityClass = 'bg-success/10 text-success'; 670 priorityText = '低'; 671 break; 672 } 673 674 // 类型样式 675 let typeClass = ''; 676 677 switch(order.type) { 678 case '维修': 679 typeClass = 'bg-warning/10 text-warning'; 680 break; 681 case '保养': 682 typeClass = 'bg-primary/10 text-primary'; 683 break; 684 } 685 686 row.innerHTML = ` 687 <td class="py-3 px-4 text-sm font-medium">${order.id}</td> 688 <td class="py-3 px-4 text-sm">${order.equipment}</td> 689 <td class="py-3 px-4"> 690 <span class="px-2 py-1 text-xs rounded-full ${typeClass}">${order.type}</span> 691 </td> 692 <td class="py-3 px-4"> 693 <span class="px-2 py-1 text-xs rounded-full ${priorityClass}">${priorityText}</span> 694 </td> 695 <td class="py-3 px-4 text-sm">${order.deadline}</td> 696 `; 697 698 container.appendChild(row); 699 }); 700 } 701 702 // 渲染今日保养计划 703 function renderMaintenanceSchedule() { 704 const container = document.getElementById('maintenance-schedule'); 705 const { maintenanceSchedule } = mockData; 706 707 container.innerHTML = ''; 708 709 maintenanceSchedule.forEach(schedule => { 710 const item = document.createElement('div'); 711 item.className = 'flex items-center p-3 rounded-lg hover:bg-gray-50 transition-colors'; 712 713 // 状态样式 714 let statusClass = ''; 715 let statusIcon = ''; 716 717 switch(schedule.status) { 718 case '已完成': 719 statusClass = 'bg-success/10 text-success'; 720 statusIcon = 'fa-check'; 721 break; 722 case '进行中': 723 statusClass = 'bg-primary/10 text-primary'; 724 statusIcon = 'fa-spinner fa-spin'; 725 break; 726 case '待开始': 727 statusClass = 'bg-gray-100 text-gray-500'; 728 statusIcon = 'fa-clock-o'; 729 break; 730 } 731 732 item.innerHTML = ` 733 <div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-500 mr-3"> 734 <i class="fa fa-cog"></i> 735 </div> 736 <div class="flex-1"> 737 <div class="text-sm font-medium">${schedule.equipment}</div> 738 <div class="text-xs text-gray-500 mt-1">${schedule.person} · ${schedule.time}</div> 739 </div> 740 <div class="w-8 h-8 rounded-full ${statusClass} flex items-center justify-center"> 741 <i class="fa ${statusIcon}"></i> 742 </div> 743 `; 744 745 container.appendChild(item); 746 }); 747 } 748 749 // 渲染人员工作负荷 750 function renderPersonnelLoad() { 751 const container = document.getElementById('personnel-load-container'); 752 const { personnelLoad } = mockData; 753 754 container.innerHTML = ''; 755 756 personnelLoad.forEach(person => { 757 const card = document.createElement('div'); 758 const total = person.completed + person.pending; 759 const loadPercentage = (total / person.capacity) * 100; 760 761 // 负荷等级样式 762 let loadClass = ''; 763 764 if (loadPercentage > 80) { 765 loadClass = 'text-danger'; 766 } else if (loadPercentage > 60) { 767 loadClass = 'text-warning'; 768 } else { 769 loadClass = 'text-success'; 770 } 771 772 card.className = 'bg-white border border-gray-100 rounded-lg p-4 hover-scale'; 773 774 card.innerHTML = ` 775 <div class="flex justify-between items-center mb-3"> 776 <div class="text-sm font-medium">${person.name}</div> 777 <div class="text-sm font-bold ${loadClass}">${loadPercentage.toFixed(0)}%</div> 778 </div> 779 <div class="w-full bg-gray-100 rounded-full h-2"> 780 <div class="bg-primary h-2 rounded-full progress-bar" style="width: ${loadPercentage}%"></div> 781 </div> 782 <div class="flex justify-between mt-2 text-xs text-gray-500"> 783 <div>已完成: <span class="font-medium text-success">${person.completed}</span></div> 784 <div>待处理: <span class="font-medium text-warning">${person.pending}</span></div> 785 <div>总容量: <span class="font-medium">${person.capacity}</span></div> 786 </div> 787 `; 788 789 container.appendChild(card); 790 }); 791 } 792 793 // 渲染设备运行详情 794 function renderEquipmentDetails() { 795 const container = document.getElementById('equipment-details'); 796 const { equipmentDetails } = mockData; 797 798 container.innerHTML = ''; 799 800 equipmentDetails.forEach(equipment => { 801 const row = document.createElement('tr'); 802 row.className = 'border-b border-gray-50 hover:bg-gray-50 transition-colors'; 803 804 // 状态样式 805 let statusClass = ''; 806 let statusText = ''; 807 808 switch(equipment.status) { 809 case '运行中': 810 statusClass = 'bg-success/10 text-success'; 811 statusText = '运行中'; 812 break; 813 case '待维修': 814 statusClass = 'bg-warning/10 text-warning'; 815 statusText = '待维修'; 816 break; 817 case '故障中': 818 statusClass = 'bg-danger/10 text-danger'; 819 statusText = '故障中'; 820 break; 821 case '闲置中': 822 statusClass = 'bg-gray-100 text-gray-500'; 823 statusText = '闲置中'; 824 break; 825 } 826 827 // OEE样式 828 let oeeClass = ''; 829 830 if (equipment.oee >= 90) { 831 oeeClass = 'text-success'; 832 } else if (equipment.oee >= 75) { 833 oeeClass = 'text-warning'; 834 } else if (equipment.oee > 0) { 835 oeeClass = 'text-danger'; 836 } 837 838 row.innerHTML = ` 839 <td class="py-3 px-4 text-sm font-medium">${equipment.id}</td> 840 <td class="py-3 px-4 text-sm">${equipment.name}</td> 841 <td class="py-3 px-4 text-sm">${equipment.location}</td> 842 <td class="py-3 px-4"> 843 <span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span> 844 </td> 845 <td class="py-3 px-4 text-sm font-medium ${oeeClass}">${equipment.oee}%</td> 846 <td class="py-3 px-4 text-sm">${equipment.runtime}</td> 847 <td class="py-3 px-4"> 848 <button class="text-primary hover:text-primary/80 mr-2"><i class="fa fa-eye"></i></button> 849 <button class="text-gray-500 hover:text-gray-700"><i class="fa fa-ellipsis-v"></i></button> 850 </td> 851 `; 852 853 container.appendChild(row); 854 }); 855 } 856 857 // 页面滚动时头部导航栏效果 858 function handleScroll() { 859 const header = document.getElementById('main-header'); 860 if (window.scrollY > 10) { 861 header.classList.add('py-2'); 862 header.classList.remove('py-4'); 863 } else { 864 header.classList.add('py-4'); 865 header.classList.remove('py-2'); 866 } 867 } 868 869 // 初始化页面 870 function initPage() { 871 // 更新统计数据 872 updateStats(); 873 874 // 渲染图表 875 renderEquipmentStatusChart(); 876 renderOeeTrendChart(); 877 878 // 渲染列表数据 879 renderPendingWorkorders(); 880 renderMaintenanceSchedule(); 881 renderPersonnelLoad(); 882 renderEquipmentDetails(); 883 884 // 添加滚动事件监听 885 window.addEventListener('scroll', handleScroll); 886 887 // 添加卡片动画效果 888 const cards = document.querySelectorAll('.stat-card'); 889 cards.forEach((card, index) => { 890 card.style.opacity = '0'; 891 card.style.transform = 'translateY(20px)'; 892 setTimeout(() => { 893 card.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; 894 card.style.opacity = '1'; 895 card.style.transform = 'translateY(0)'; 896 }, 100 * index); 897 }); 898 } 899 900 // 页面加载完成后初始化 901 window.addEventListener('load', initPage); 902 </script> 903</body> 904</html>

部署步骤

  • 清理 Nginx 默认测试页面:
1sudo rm /var/www/html/index.nginx-debian.html 2
  • 上传 HTML 文件到 Nginx 根目录:
  • 将本地的设备监控 HTML 文件(命名为index.html)复制到 Nginx 默认根目录/var/www/html/
  • 命令行上传(本地终端执行,替换用户名和服务器 IP):
1scp /本地文件路径/index.html 用户名@IOT2050IP:/var/www/html/ 2
1sudo systemctl restart nginx 2
  • 访问测试:
  • 本地访问(IOT2050 自带屏幕):打开浏览器输入 http://localhost
  • 远程访问(同一局域网):输入 http://IOT2050IP(无需加端口,80 为 HTTP 默认端口)
  • 防火墙配置(可选,解决无法访问问题):
1sudo ufw allow 80/tcp # 允许80端口外部访问 2sudo ufw enable # 启用防火墙(若未启用) 3sudo ufw status # 验证规则是否生效 4

页面效果预览

  • 顶部导航:包含搜索、通知、用户中心功能
  • 统计卡片:设备总数、运行中设备、待维修设备、平均 OEE
  • 图表模块:设备状态分布饼图、30 天 OEE 趋势线图
  • 数据列表:待处理工单、今日保养计划、设备运行详情

三、部署Asp.net Core 能源监控系统

前端主页面代码

1<!DOCTYPE html> 2<html lang="zh-CN"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>设备管理系统看板</title> 7 <!-- Tailwind CSS --> 8 <script src="https://cdn.tailwindcss.com"></script> 9 <!-- Font Awesome --> 10 <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet"> 11 <!-- Chart.js --> 12 <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script> 13 14 <!-- Tailwind配置 --> 15 <script> 16 tailwind.config = { 17 theme: { 18 extend: { 19 colors: { 20 primary: '#165DFF', 21 secondary: '#0FC6C2', 22 success: '#00B42A', 23 warning: '#FF7D00', 24 danger: '#F53F3F', 25 info: '#86909C', 26 dark: '#1D2129', 27 light: '#F2F3F5' 28 }, 29 fontFamily: { 30 inter: ['Inter', 'system-ui', 'sans-serif'], 31 }, 32 animation: { 33 'fade-in': 'fadeIn 0.5s ease-in-out', 34 'slide-up': 'slideUp 0.5s ease-out', 35 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', 36 }, 37 keyframes: { 38 fadeIn: { 39 '0%': { opacity: '0' }, 40 '100%': { opacity: '1' }, 41 }, 42 slideUp: { 43 '0%': { transform: 'translateY(20px)', opacity: '0' }, 44 '100%': { transform: 'translateY(0)', opacity: '1' }, 45 } 46 } 47 }, 48 } 49 } 50 </script> 51 52 <!-- 自定义工具类 --> 53 <style type="text/tailwindcss"> 54 @layer utilities { 55 .content-auto { 56 content-visibility: auto; 57 } 58 .scrollbar-hide { 59 -ms-overflow-style: none; 60 scrollbar-width: none; 61 } 62 .scrollbar-hide::-webkit-scrollbar { 63 display: none; 64 } 65 .card-shadow { 66 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); 67 } 68 .hover-scale { 69 transition: transform 0.2s ease; 70 } 71 .hover-scale:hover { 72 transform: scale(1.02); 73 } 74 .gradient-bg { 75 background: linear-gradient(135deg, #165DFF 0%, #0FC6C2 100%); 76 } 77 } 78 </style> 79 80 <style> 81 /* 基础样式 */ 82 body { 83 font-family: 'Inter', system-ui, sans-serif; 84 overflow-x: hidden; 85 } 86 87 /* 平滑滚动 */ 88 html { 89 scroll-behavior: smooth; 90 } 91 92 /* 表格样式优化 */ 93 .data-table th { 94 font-weight: 600; 95 text-transform: uppercase; 96 font-size: 0.75rem; 97 letter-spacing: 0.05em; 98 } 99 100 /* 进度条动画 */ 101 .progress-bar { 102 transition: width 1s ease-in-out; 103 } 104 105 /* 卡片悬停效果 */ 106 .stat-card { 107 transition: all 0.3s ease; 108 } 109 .stat-card:hover { 110 box-shadow: 0 10px 30px rgba(22, 93, 255, 0.15); 111 transform: translateY(-5px); 112 } 113 </style> 114</head> 115<body class="bg-light min-h-screen"> 116 <!-- 顶部导航 --> 117 <header class="bg-white shadow-md fixed w-full top-0 z-50 transition-all duration-300" id="main-header"> 118 <div class="container mx-auto px-4"> 119 <div class="flex justify-between items-center py-4"> 120 <!-- 左侧Logo --> 121 <div class="flex items-center space-x-2"> 122 <div class="gradient-bg text-white p-2 rounded-lg"> 123 <i class="fa fa-cogs text-xl"></i> 124 </div> 125 <h1 class="text-xl font-bold text-dark">设备管理系统</h1> 126 </div> 127 128 <!-- 中间搜索 --> 129 <div class="hidden md:block flex-1 max-w-md mx-8"> 130 <div class="relative"> 131 <input type="text" placeholder="搜索设备、工单或人员..." 132 class="w-full py-2 pl-10 pr-4 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"> 133 <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i> 134 </div> 135 </div> 136 137 <!-- 右侧工具栏 --> 138 <div class="flex items-center space-x-4"> 139 <!-- 通知 --> 140 <button class="relative p-2 rounded-full hover:bg-gray-100 transition-colors"> 141 <i class="fa fa-bell text-gray-600"></i> 142 <span class="absolute top-0 right-0 w-2 h-2 bg-danger rounded-full"></span> 143 </button> 144 145 <!-- 设置 --> 146 <button class="p-2 rounded-full hover:bg-gray-100 transition-colors"> 147 <i class="fa fa-cog text-gray-600"></i> 148 </button> 149 150 <!-- 用户 --> 151 <div class="flex items-center space-x-2 cursor-pointer group"> 152 <img src="https://picsum.photos/id/1005/200/200" alt="用户头像" class="w-8 h-8 rounded-full object-cover border-2 border-transparent group-hover:border-primary transition-all"> 153 <span class="hidden md:inline text-sm font-medium text-gray-700">管理员</span> 154 <i class="fa fa-angle-down text-gray-500 group-hover:text-primary transition-colors"></i> 155 </div> 156 </div> 157 </div> 158 </div> 159 </header> 160 161 <!-- 主内容区域 --> 162 <main class="container mx-auto px-4 pt-24 pb-12"> 163 <!-- 页面标题和统计概览 --> 164 <div class="mb-8"> 165 <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"> 166 <div> 167 <h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-dark">设备管理看板</h2> 168 <p class="text-gray-500 mt-1">实时监控设备运行状态、效率和维护情况</p> 169 </div> 170 <div class="flex space-x-3 mt-4 md:mt-0"> 171 <button class="px-4 py-2 bg-white border border-gray-200 rounded-lg shadow-sm hover:bg-gray-50 transition-colors flex items-center"> 172 <i class="fa fa-download mr-2 text-gray-600"></i> 173 <span>导出报告</span> 174 </button> 175 <button class="px-4 py-2 bg-primary text-white rounded-lg shadow-sm hover:bg-primary/90 transition-colors flex items-center"> 176 <i class="fa fa-refresh mr-2"></i> 177 <span>刷新数据</span> 178 </button> 179 </div> 180 </div> 181 182 <!-- 状态卡片 --> 183 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6"> 184 <!-- 设备总数 --> 185 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 186 <div class="flex justify-between items-start"> 187 <div> 188 <p class="text-gray-500 text-sm">设备总数</p> 189 <h3 class="text-3xl font-bold mt-1" id="total-equipment">0</h3> 190 <div class="flex items-center mt-2 text-success text-sm"> 191 <i class="fa fa-arrow-up mr-1"></i> 192 <span>2台 (本周)</span> 193 </div> 194 </div> 195 <div class="bg-primary/10 p-3 rounded-lg"> 196 <i class="fa fa-machine text-primary text-xl"></i> 197 </div> 198 </div> 199 </div> 200 201 <!-- 运行中设备 --> 202 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 203 <div class="flex justify-between items-start"> 204 <div> 205 <p class="text-gray-500 text-sm">运行中设备</p> 206 <h3 class="text-3xl font-bold mt-1" id="running-equipment">0</h3> 207 <div class="flex items-center mt-2 text-success text-sm"> 208 <i class="fa fa-arrow-up mr-1"></i> 209 <span>87%</span> 210 </div> 211 </div> 212 <div class="bg-success/10 p-3 rounded-lg"> 213 <i class="fa fa-play text-success text-xl"></i> 214 </div> 215 </div> 216 </div> 217 218 <!-- 待维修设备 --> 219 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 220 <div class="flex justify-between items-start"> 221 <div> 222 <p class="text-gray-500 text-sm">待维修设备</p> 223 <h3 class="text-3xl font-bold mt-1" id="maintenance-equipment">0</h3> 224 <div class="flex items-center mt-2 text-danger text-sm"> 225 <i class="fa fa-arrow-up mr-1"></i> 226 <span>2台 (较上周)</span> 227 </div> 228 </div> 229 <div class="bg-warning/10 p-3 rounded-lg"> 230 <i class="fa fa-wrench text-warning text-xl"></i> 231 </div> 232 </div> 233 </div> 234 235 <!-- 平均OEE --> 236 <div class="stat-card bg-white rounded-xl p-6 card-shadow"> 237 <div class="flex justify-between items-start"> 238 <div> 239 <p class="text-gray-500 text-sm">平均OEE</p> 240 <h3 class="text-3xl font-bold mt-1" id="average-oee">0%</h3> 241 <div class="flex items-center mt-2 text-success text-sm"> 242 <i class="fa fa-arrow-up mr-1"></i> 243 <span>3.2% (较上月)</span> 244 </div> 245 </div> 246 <div class="bg-secondary/10 p-3 rounded-lg"> 247 <i class="fa fa-line-chart text-secondary text-xl"></i> 248 </div> 249 </div> 250 </div> 251 </div> 252 </div> 253 254 <!-- 主要图表区域 --> 255 <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> 256 <!-- 设备状态分布 --> 257 <div class="lg:col-span-1 bg-white rounded-xl p-6 card-shadow"> 258 <div class="flex justify-between items-center mb-4"> 259 <h3 class="font-bold text-lg text-dark">设备状态分布</h3> 260 <div class="flex space-x-2"> 261 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 262 </div> 263 </div> 264 <div class="h-64 flex justify-center items-center"> 265 <canvas id="equipment-status-chart"></canvas> 266 </div> 267 <div class="grid grid-cols-2 gap-2 mt-4"> 268 <div class="flex items-center space-x-2"> 269 <span class="w-3 h-3 rounded-full bg-success"></span> 270 <span class="text-sm text-gray-600">运行中</span> 271 </div> 272 <div class="flex items-center space-x-2"> 273 <span class="w-3 h-3 rounded-full bg-warning"></span> 274 <span class="text-sm text-gray-600">待维修</span> 275 </div> 276 <div class="flex items-center space-x-2"> 277 <span class="w-3 h-3 rounded-full bg-danger"></span> 278 <span class="text-sm text-gray-600">故障中</span> 279 </div> 280 <div class="flex items-center space-x-2"> 281 <span class="w-3 h-3 rounded-full bg-info"></span> 282 <span class="text-sm text-gray-600">闲置中</span> 283 </div> 284 </div> 285 </div> 286 287 <!-- OEE趋势图 --> 288 <div class="lg:col-span-2 bg-white rounded-xl p-6 card-shadow"> 289 <div class="flex justify-between items-center mb-4"> 290 <h3 class="font-bold text-lg text-dark">OEE趋势 (近30天)</h3> 291 <div class="flex space-x-2"> 292 <button class="px-2 py-1 text-xs rounded-md bg-primary/10 text-primary"></button> 293 <button class="px-2 py-1 text-xs rounded-md hover:bg-gray-100"></button> 294 <button class="px-2 py-1 text-xs rounded-md hover:bg-gray-100"></button> 295 <button class="p-1 rounded hover:bg-gray-100 ml-2"><i class="fa fa-download text-gray-500"></i></button> 296 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 297 </div> 298 </div> 299 <div class="h-64"> 300 <canvas id="oee-trend-chart"></canvas> 301 </div> 302 </div> 303 </div> 304 305 <!-- 工单和计划区域 --> 306 <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> 307 <!-- 待处理工单 --> 308 <div class="bg-white rounded-xl p-6 card-shadow"> 309 <div class="flex justify-between items-center mb-4"> 310 <h3 class="font-bold text-lg text-dark">待处理工单</h3> 311 <div class="flex space-x-2"> 312 <select class="text-sm border-none bg-transparent focus:outline-none focus:ring-0 text-gray-500"> 313 <option>全部</option> 314 <option>维修工单</option> 315 <option>保养工单</option> 316 </select> 317 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 318 </div> 319 </div> 320 <div class="overflow-x-auto"> 321 <table class="data-table w-full"> 322 <thead> 323 <tr class="border-b border-gray-100"> 324 <th class="py-3 px-4 text-left text-gray-500">工单编号</th> 325 <th class="py-3 px-4 text-left text-gray-500">设备名称</th> 326 <th class="py-3 px-4 text-left text-gray-500">类型</th> 327 <th class="py-3 px-4 text-left text-gray-500">优先级</th> 328 <th class="py-3 px-4 text-left text-gray-500">截止日期</th> 329 </tr> 330 </thead> 331 <tbody id="pending-workorders"> 332 <!-- 动态生成 --> 333 </tbody> 334 </table> 335 </div> 336 <div class="mt-4 text-center"> 337 <button class="text-primary hover:text-primary/80 text-sm font-medium">查看全部工单 <i class="fa fa-angle-right ml-1"></i></button> 338 </div> 339 </div> 340 341 <!-- 今日保养计划 --> 342 <div class="bg-white rounded-xl p-6 card-shadow"> 343 <div class="flex justify-between items-center mb-4"> 344 <h3 class="font-bold text-lg text-dark">今日保养计划</h3> 345 <div class="flex space-x-2"> 346 <button class="px-3 py-1 text-sm rounded-md bg-primary/10 text-primary flex items-center"> 347 <i class="fa fa-plus mr-1"></i> 添加计划 348 </button> 349 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 350 </div> 351 </div> 352 <div class="space-y-4" id="maintenance-schedule"> 353 <!-- 动态生成 --> 354 </div> 355 <div class="mt-4 text-center"> 356 <button class="text-primary hover:text-primary/80 text-sm font-medium">查看全部计划 <i class="fa fa-angle-right ml-1"></i></button> 357 </div> 358 </div> 359 </div> 360 361 <!-- 人员工作负荷区域 --> 362 <div class="bg-white rounded-xl p-6 card-shadow mb-8"> 363 <div class="flex justify-between items-center mb-4"> 364 <h3 class="font-bold text-lg text-dark">保养人员工作负荷</h3> 365 <div class="flex space-x-2"> 366 <button class="px-2 py-1 text-xs rounded-md bg-primary text-white">本周</button> 367 <button class="px-2 py-1 text-xs rounded-md hover:bg-gray-100">本月</button> 368 <button class="p-1 rounded hover:bg-gray-100 ml-2"><i class="fa fa-download text-gray-500"></i></button> 369 <button class="p-1 rounded hover:bg-gray-100"><i class="fa fa-ellipsis-v text-gray-500"></i></button> 370 </div> 371 </div> 372 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> 373 <!-- 人员负荷卡片将动态生成 --> 374 <div id="personnel-load-container"></div> 375 </div> 376 </div> 377 378 <!-- 设备详情列表 --> 379 <div class="bg-white rounded-xl p-6 card-shadow"> 380 <div class="flex justify-between items-center mb-6"> 381 <h3 class="font-bold text-lg text-dark">设备运行详情</h3> 382 <div class="flex items-center space-x-4"> 383 <div class="relative"> 384 <input type="text" placeholder="搜索设备..." 385 class="py-2 pl-10 pr-4 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary text-sm"> 386 <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"></i> 387 </div> 388 <select class="text-sm border border-gray-200 rounded-lg py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary bg-white"> 389 <option>全部状态</option> 390 <option>运行中</option> 391 <option>待维修</option> 392 <option>故障中</option> 393 <option>闲置中</option> 394 </select> 395 </div> 396 </div> 397 <div class="overflow-x-auto"> 398 <table class="data-table w-full"> 399 <thead> 400 <tr class="border-b border-gray-100"> 401 <th class="py-3 px-4 text-left text-gray-500">设备编号</th> 402 <th class="py-3 px-4 text-left text-gray-500">设备名称</th> 403 <th class="py-3 px-4 text-left text-gray-500">位置</th> 404 <th class="py-3 px-4 text-left text-gray-500">状态</th> 405 <th class="py-3 px-4 text-left text-gray-500">OEE</th> 406 <th class="py-3 px-4 text-left text-gray-500">运行时长</th> 407 <th class="py-3 px-4 text-left text-gray-500">操作</th> 408 </tr> 409 </thead> 410 <tbody id="equipment-details"> 411 <!-- 动态生成 --> 412 </tbody> 413 </table> 414 </div> 415 <div class="mt-6 flex justify-between items-center"> 416 <div class="text-sm text-gray-500">显示 1-10 项,共 <span id="total-equipment-count">0</span></div> 417 <div class="flex space-x-1"> 418 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 text-gray-400 hover:border-primary hover:text-primary transition-colors"> 419 <i class="fa fa-angle-left"></i> 420 </button> 421 <button class="w-8 h-8 flex items-center justify-center rounded-md bg-primary text-white">1</button> 422 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 hover:border-primary hover:text-primary transition-colors">2</button> 423 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 hover:border-primary hover:text-primary transition-colors">3</button> 424 <button class="w-8 h-8 flex items-center justify-center rounded-md border border-gray-200 hover:border-primary hover:text-primary transition-colors"> 425 <i class="fa fa-angle-right"></i> 426 </button> 427 </div> 428 </div> 429 </div> 430 </main> 431 432 <!-- 页脚 --> 433 <footer class="bg-white border-t border-gray-200 py-4"> 434 <div class="container mx-auto px-4"> 435 <div class="flex flex-col md:flex-row justify-between items-center"> 436 <div class="text-sm text-gray-500 mb-4 md:mb-0">© 2023 设备管理系统. 保留所有权利.</div> 437 <div class="flex space-x-4"> 438 <a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">使用帮助</a> 439 <a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">隐私政策</a> 440 <a href="#" class="text-sm text-gray-500 hover:text-primary transition-colors">联系我们</a> 441 </div> 442 </div> 443 </div> 444 </footer> 445 446 <!-- 脚本 --> 447 <script> 448 // 模拟数据 449 const mockData = { 450 // 设备统计数据 451 stats: { 452 total: 56, 453 running: 49, 454 maintenance: 3, 455 fault: 2, 456 idle: 2, 457 averageOee: 87.3 458 }, 459 460 // OEE趋势数据 (近30天) 461 oeeTrend: Array.from({length: 30}, (_, i) => ({ 462 date: [`6月${i+1}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.i.md), 463 oee: 75 + Math.random() * 20 464 })), 465 466 // 待处理工单 467 pendingWorkorders: [ 468 { id: 'WO-20230615-001', equipment: '注塑机-001', type: '维修', priority: '高', deadline: '2023-06-16' }, 469 { id: 'WO-20230615-002', equipment: '冲压机-003', type: '保养', priority: '中', deadline: '2023-06-17' }, 470 { id: 'WO-20230615-003', equipment: '包装机-005', type: '维修', priority: '中', deadline: '2023-06-18' }, 471 { id: 'WO-20230615-004', equipment: '输送带-002', type: '保养', priority: '低', deadline: '2023-06-19' }, 472 { id: 'WO-20230615-005', equipment: '焊接机-007', type: '维修', priority: '高', deadline: '2023-06-16' } 473 ], 474 475 // 今日保养计划 476 maintenanceSchedule: [ 477 { id: 'MS-20230615-001', equipment: '注塑机-001', time: '09:00', person: '张师傅', status: '已完成' }, 478 { id: 'MS-20230615-002', equipment: '冲压机-003', time: '11:30', person: '李师傅', status: '进行中' }, 479 { id: 'MS-20230615-003', equipment: '包装机-005', time: '14:00', person: '王师傅', status: '待开始' }, 480 { id: 'MS-20230615-004', equipment: '输送带-002', time: '16:30', person: '赵师傅', status: '待开始' } 481 ], 482 483 // 保养人员工作负荷 484 personnelLoad: [ 485 { name: '张师傅', completed: 8, pending: 3, capacity: 15 }, 486 { name: '李师傅', completed: 6, pending: 5, capacity: 12 }, 487 { name: '王师傅', completed: 4, pending: 2, capacity: 10 }, 488 { name: '赵师傅', completed: 5, pending: 4, capacity: 12 } 489 ], 490 491 // 设备运行详情 492 equipmentDetails: [ 493 { id: 'EQ-001', name: '注塑机-001', location: 'A车间-01', status: '运行中', oee: 92.5, runtime: '234h' }, 494 { id: 'EQ-002', name: '冲压机-001', location: 'B车间-02', status: '运行中', oee: 88.3, runtime: '198h' }, 495 { id: 'EQ-003', name: '冲压机-002', location: 'B车间-03', status: '故障中', oee: 0, runtime: '0h' }, 496 { id: 'EQ-004', name: '包装机-001', location: 'C车间-01', status: '运行中', oee: 90.1, runtime: '215h' }, 497 { id: 'EQ-005', name: '包装机-002', location: 'C车间-02', status: '待维修', oee: 76.5, runtime: '189h' }, 498 { id: 'EQ-006', name: '输送带-001', location: 'D车间-01', status: '运行中', oee: 85.7, runtime: '240h' }, 499 { id: 'EQ-007', name: '焊接机-001', location: 'E车间-01', status: '运行中', oee: 89.2, runtime: '176h' }, 500 { id: 'EQ-008', name: '焊接机-002', location: 'E车间-02', status: '闲置中', oee: 0, runtime: '0h' }, 501 { id: 'EQ-009', name: '车床-001', location: 'F车间-01', status: '运行中', oee: 91.8, runtime: '203h' }, 502 { id: 'EQ-010', name: '钻床-001', location: 'F车间-02', status: '待维修', oee: 78.3, runtime: '165h' } 503 ] 504 }; 505 506 // 更新统计数据 507 function updateStats() { 508 const { stats } = mockData; 509 document.getElementById('total-equipment').textContent = stats.total; 510 document.getElementById('running-equipment').textContent = stats.running; 511 document.getElementById('maintenance-equipment').textContent = stats.maintenance; 512 document.getElementById('average-oee').textContent = `${stats.averageOee}%`; 513 document.getElementById('total-equipment-count').textContent = mockData.equipmentDetails.length; 514 } 515 516 // 渲染设备状态图表 517 function renderEquipmentStatusChart() { 518 const { stats } = mockData; 519 const ctx = document.getElementById('equipment-status-chart').getContext('2d'); 520 521 new Chart(ctx, { 522 type: 'doughnut', 523 data: { 524 labels: ['运行中', '待维修', '故障中', '闲置中'], 525 datasets: [{ 526 data: [stats.running, stats.maintenance, stats.fault, stats.idle], 527 backgroundColor: ['#00B42A', '#FF7D00', '#F53F3F', '#86909C'], 528 borderWidth: 0, 529 hoverOffset: 5 530 }] 531 }, 532 options: { 533 responsive: true, 534 maintainAspectRatio: false, 535 plugins: { 536 legend: { 537 display: false 538 }, 539 tooltip: { 540 callbacks: { 541 label: function(context) { 542 const label = context.label || ''; 543 const value = context.raw || 0; 544 const total = mockData.stats.total; 545 const percentage = Math.round((value / total) * 100); 546 return [`${label}: ${value}台 (${percentage}%)`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.label.md); 547 } 548 } 549 } 550 }, 551 cutout: '70%' 552 } 553 }); 554 } 555 556 // 渲染OEE趋势图表 557 function renderOeeTrendChart() { 558 const { oeeTrend } = mockData; 559 const ctx = document.getElementById('oee-trend-chart').getContext('2d'); 560 561 new Chart(ctx, { 562 type: 'line', 563 data: { 564 labels: oeeTrend.map(item => item.date), 565 datasets: [{ 566 label: 'OEE值', 567 data: oeeTrend.map(item => item.oee), 568 borderColor: '#165DFF', 569 backgroundColor: 'rgba(22, 93, 255, 0.1)', 570 borderWidth: 2, 571 tension: 0.3, 572 fill: true, 573 pointRadius: 0, 574 pointHoverRadius: 4, 575 pointBackgroundColor: '#165DFF', 576 pointHoverBackgroundColor: '#ffffff', 577 pointHoverBorderColor: '#165DFF', 578 pointHoverBorderWidth: 2 579 }] 580 }, 581 options: { 582 responsive: true, 583 maintainAspectRatio: false, 584 interaction: { 585 mode: 'index', 586 intersect: false, 587 }, 588 plugins: { 589 legend: { 590 display: false 591 }, 592 tooltip: { 593 backgroundColor: 'rgba(255, 255, 255, 0.95)', 594 titleColor: '#1D2129', 595 bodyColor: '#86909C', 596 borderColor: 'rgba(0, 0, 0, 0.05)', 597 borderWidth: 1, 598 padding: 10, 599 boxPadding: 5, 600 usePointStyle: true, 601 callbacks: { 602 label: function(context) { 603 return `OEE: ${context.raw.toFixed(1)}%`; 604 } 605 } 606 } 607 }, 608 scales: { 609 x: { 610 grid: { 611 display: false 612 }, 613 ticks: { 614 maxRotation: 0, 615 autoSkip: true, 616 maxTicksLimit: 10, 617 color: '#86909C', 618 font: { 619 size: 10 620 } 621 } 622 }, 623 y: { 624 beginAtZero: false, 625 min: 70, 626 grid: { 627 color: 'rgba(0, 0, 0, 0.03)' 628 }, 629 ticks: { 630 color: '#86909C', 631 font: { 632 size: 10 633 }, 634 callback: function(value) { 635 return value + '%'; 636 } 637 } 638 } 639 } 640 } 641 }); 642 } 643 644 // 渲染待处理工单 645 function renderPendingWorkorders() { 646 const container = document.getElementById('pending-workorders'); 647 const { pendingWorkorders } = mockData; 648 649 container.innerHTML = ''; 650 651 pendingWorkorders.forEach(order => { 652 const row = document.createElement('tr'); 653 row.className = 'border-b border-gray-50 hover:bg-gray-50 transition-colors'; 654 655 // 优先级样式 656 let priorityClass = ''; 657 let priorityText = ''; 658 659 switch(order.priority) { 660 case '高': 661 priorityClass = 'bg-danger/10 text-danger'; 662 priorityText = '高'; 663 break; 664 case '中': 665 priorityClass = 'bg-warning/10 text-warning'; 666 priorityText = '中'; 667 break; 668 case '低': 669 priorityClass = 'bg-success/10 text-success'; 670 priorityText = '低'; 671 break; 672 } 673 674 // 类型样式 675 let typeClass = ''; 676 677 switch(order.type) { 678 case '维修': 679 typeClass = 'bg-warning/10 text-warning'; 680 break; 681 case '保养': 682 typeClass = 'bg-primary/10 text-primary'; 683 break; 684 } 685 686 row.innerHTML = ` 687 <td class="py-3 px-4 text-sm font-medium">${order.id}</td> 688 <td class="py-3 px-4 text-sm">${order.equipment}</td> 689 <td class="py-3 px-4"> 690 <span class="px-2 py-1 text-xs rounded-full ${typeClass}">${order.type}</span> 691 </td> 692 <td class="py-3 px-4"> 693 <span class="px-2 py-1 text-xs rounded-full ${priorityClass}">${priorityText}</span> 694 </td> 695 <td class="py-3 px-4 text-sm">${order.deadline}</td> 696 `; 697 698 container.appendChild(row); 699 }); 700 } 701 702 // 渲染今日保养计划 703 function renderMaintenanceSchedule() { 704 const container = document.getElementById('maintenance-schedule'); 705 const { maintenanceSchedule } = mockData; 706 707 container.innerHTML = ''; 708 709 maintenanceSchedule.forEach(schedule => { 710 const item = document.createElement('div'); 711 item.className = 'flex items-center p-3 rounded-lg hover:bg-gray-50 transition-colors'; 712 713 // 状态样式 714 let statusClass = ''; 715 let statusIcon = ''; 716 717 switch(schedule.status) { 718 case '已完成': 719 statusClass = 'bg-success/10 text-success'; 720 statusIcon = 'fa-check'; 721 break; 722 case '进行中': 723 statusClass = 'bg-primary/10 text-primary'; 724 statusIcon = 'fa-spinner fa-spin'; 725 break; 726 case '待开始': 727 statusClass = 'bg-gray-100 text-gray-500'; 728 statusIcon = 'fa-clock-o'; 729 break; 730 } 731 732 item.innerHTML = ` 733 <div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-500 mr-3"> 734 <i class="fa fa-cog"></i> 735 </div> 736 <div class="flex-1"> 737 <div class="text-sm font-medium">${schedule.equipment}</div> 738 <div class="text-xs text-gray-500 mt-1">${schedule.person} · ${schedule.time}</div> 739 </div> 740 <div class="w-8 h-8 rounded-full ${statusClass} flex items-center justify-center"> 741 <i class="fa ${statusIcon}"></i> 742 </div> 743 `; 744 745 container.appendChild(item); 746 }); 747 } 748 749 // 渲染人员工作负荷 750 function renderPersonnelLoad() { 751 const container = document.getElementById('personnel-load-container'); 752 const { personnelLoad } = mockData; 753 754 container.innerHTML = ''; 755 756 personnelLoad.forEach(person => { 757 const card = document.createElement('div'); 758 const total = person.completed + person.pending; 759 const loadPercentage = (total / person.capacity) * 100; 760 761 // 负荷等级样式 762 let loadClass = ''; 763 764 if (loadPercentage > 80) { 765 loadClass = 'text-danger'; 766 } else if (loadPercentage > 60) { 767 loadClass = 'text-warning'; 768 } else { 769 loadClass = 'text-success'; 770 } 771 772 card.className = 'bg-white border border-gray-100 rounded-lg p-4 hover-scale'; 773 774 card.innerHTML = ` 775 <div class="flex justify-between items-center mb-3"> 776 <div class="text-sm font-medium">${person.name}</div> 777 <div class="text-sm font-bold ${loadClass}">${loadPercentage.toFixed(0)}%</div> 778 </div> 779 <div class="w-full bg-gray-100 rounded-full h-2"> 780 <div class="bg-primary h-2 rounded-full progress-bar" style="width: ${loadPercentage}%"></div> 781 </div> 782 <div class="flex justify-between mt-2 text-xs text-gray-500"> 783 <div>已完成: <span class="font-medium text-success">${person.completed}</span></div> 784 <div>待处理: <span class="font-medium text-warning">${person.pending}</span></div> 785 <div>总容量: <span class="font-medium">${person.capacity}</span></div> 786 </div> 787 `; 788 789 container.appendChild(card); 790 }); 791 } 792 793 // 渲染设备运行详情 794 function renderEquipmentDetails() { 795 const container = document.getElementById('equipment-details'); 796 const { equipmentDetails } = mockData; 797 798 container.innerHTML = ''; 799 800 equipmentDetails.forEach(equipment => { 801 const row = document.createElement('tr'); 802 row.className = 'border-b border-gray-50 hover:bg-gray-50 transition-colors'; 803 804 // 状态样式 805 let statusClass = ''; 806 let statusText = ''; 807 808 switch(equipment.status) { 809 case '运行中': 810 statusClass = 'bg-success/10 text-success'; 811 statusText = '运行中'; 812 break; 813 case '待维修': 814 statusClass = 'bg-warning/10 text-warning'; 815 statusText = '待维修'; 816 break; 817 case '故障中': 818 statusClass = 'bg-danger/10 text-danger'; 819 statusText = '故障中'; 820 break; 821 case '闲置中': 822 statusClass = 'bg-gray-100 text-gray-500'; 823 statusText = '闲置中'; 824 break; 825 } 826 827 // OEE样式 828 let oeeClass = ''; 829 830 if (equipment.oee >= 90) { 831 oeeClass = 'text-success'; 832 } else if (equipment.oee >= 75) { 833 oeeClass = 'text-warning'; 834 } else if (equipment.oee > 0) { 835 oeeClass = 'text-danger'; 836 } 837 838 row.innerHTML = ` 839 <td class="py-3 px-4 text-sm font-medium">${equipment.id}</td> 840 <td class="py-3 px-4 text-sm">${equipment.name}</td> 841 <td class="py-3 px-4 text-sm">${equipment.location}</td> 842 <td class="py-3 px-4"> 843 <span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span> 844 </td> 845 <td class="py-3 px-4 text-sm font-medium ${oeeClass}">${equipment.oee}%</td> 846 <td class="py-3 px-4 text-sm">${equipment.runtime}</td> 847 <td class="py-3 px-4"> 848 <button class="text-primary hover:text-primary/80 mr-2"><i class="fa fa-eye"></i></button> 849 <button class="text-gray-500 hover:text-gray-700"><i class="fa fa-ellipsis-v"></i></button> 850 </td> 851 `; 852 853 container.appendChild(row); 854 }); 855 } 856 857 // 页面滚动时头部导航栏效果 858 function handleScroll() { 859 const header = document.getElementById('main-header'); 860 if (window.scrollY > 10) { 861 header.classList.add('py-2'); 862 header.classList.remove('py-4'); 863 } else { 864 header.classList.add('py-4'); 865 header.classList.remove('py-2'); 866 } 867 } 868 869 // 初始化页面 870 function initPage() { 871 // 更新统计数据 872 updateStats(); 873 874 // 渲染图表 875 renderEquipmentStatusChart(); 876 renderOeeTrendChart(); 877 878 // 渲染列表数据 879 renderPendingWorkorders(); 880 renderMaintenanceSchedule(); 881 renderPersonnelLoad(); 882 renderEquipmentDetails(); 883 884 // 添加滚动事件监听 885 window.addEventListener('scroll', handleScroll); 886 887 // 添加卡片动画效果 888 const cards = document.querySelectorAll('.stat-card'); 889 cards.forEach((card, index) => { 890 card.style.opacity = '0'; 891 card.style.transform = 'translateY(20px)'; 892 setTimeout(() => { 893 card.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; 894 card.style.opacity = '1'; 895 card.style.transform = 'translateY(0)'; 896 }, 100 * index); 897 }); 898 } 899 900 // 页面加载完成后初始化 901 window.addEventListener('load', initPage); 902 </script> 903</body> 904</html>

后端业务数据获取与统计代码

1using Microsoft.AspNetCore.Mvc; 2using EnergyMonitorDashboard.Models; 3using EnergyMonitorDashboard.Services; 4using System.Diagnostics; 5 6namespace EnergyMonitorDashboard.Controllers; 7 8public class HomeController : Controller 9{ 10 private readonly ILogger<HomeController> _logger; 11 private readonly IEnergyMonitorService _energyService; 12 13 public HomeController(ILogger<HomeController> logger, IEnergyMonitorService energyService) 14 { 15 _logger = logger; 16 _energyService = energyService; 17 } 18 19 public async Task<IActionResult> Index() 20 { 21 // 初始化数据库 22 await _energyService.InitializeDatabaseAsync(); 23 24 // 获取所有设备信息 25 var devices = await _energyService.GetAllDevicesAsync(); 26 return View(devices); 27 } 28 29 public IActionResult Privacy() 30 { 31 return View(); 32 } 33 34 [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 35 public IActionResult Error() 36 { 37 return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 38 } 39 40 // 获取设备详情 41 public async Task<IActionResult> DeviceDetail(int id) 42 { 43 var device = await _energyService.GetDeviceByIdAsync(id); 44 if (device == null) 45 { 46 return NotFound(); 47 } 48 return View(device); 49 } 50 51 // 获取设备能源数据 52 [HttpGet] 53 public async Task<IActionResult> GetDeviceEnergyData(int deviceId, int days = 7) 54 { 55 var energyData = await _energyService.GetDeviceEnergyDataAsync(deviceId, days); 56 return Json(energyData); 57 } 58 59 // 获取能源趋势数据 60 [HttpGet] 61 public async Task<IActionResult> GetDeviceEnergyTrend(int deviceId, string timeRange = "week") 62 { 63 var days = timeRange switch 64 { 65 "day" => 1, 66 "week" => 7, 67 "month" => 30, 68 _ => 7 69 }; 70 var trendData = await _energyService.GetDeviceEnergyDataAsync(deviceId, days); 71 return Json(trendData); 72 } 73 74 // 更新设备状态 75 [HttpPost] 76 public async Task<IActionResult> UpdateDeviceStatus(int deviceId, string status, bool isActive) 77 { 78 await _energyService.UpdateDeviceStatusAsync(deviceId, status, isActive); 79 return Ok(new { success = true }); 80 } 81 82 // 获取设备统计数据 83 [HttpGet] 84 public async Task<IActionResult> GetDeviceStatistics(int deviceId) 85 { 86 var statistics = await _energyService.GetDeviceStatisticsAsync(deviceId); 87 return Json(statistics); 88 } 89 90 // 获取总体能源趋势数据(所有设备) 91 [HttpGet] 92 public IActionResult GetEnergyTrend(string timeRange = "week") 93 { 94 _logger.LogInformation($"GetEnergyTrend API 被调用,timeRange: {timeRange}"); 95 96 var days = timeRange switch 97 { 98 "day" => 1, 99 "week" => 7, 100 "month" => 30, 101 _ => 7 102 }; 103 104 _logger.LogInformation($"获取{days}天的数据"); 105 106 // 直接生成测试数据,避免数据量过大 107 var testData = GenerateTestTrendData(days); 108 _logger.LogInformation($"生成的测试数据数量: {testData.Count}"); 109 110 // 限制返回的数据点数量,避免图表渲染问题 111 int maxPoints = days == 1 ? 24 : days == 7 ? 56 : 120; 112 var limitedData = testData.Take(maxPoints).ToList(); 113 _logger.LogInformation($"限制后的数据点数量: {limitedData.Count}"); 114 115 _logger.LogInformation("GetEnergyTrend API 返回数据成功"); 116 return Json(limitedData); 117 } 118 119 // 生成测试能源趋势数据 120 private List<object> GenerateTestTrendData(int days) 121 { 122 var data = new List<object>(); 123 var random = new Random(); 124 var endDate = DateTime.Now; 125 var startDate = endDate.AddDays(-days); 126 var interval = days <= 1 ? TimeSpan.FromHours(1) : TimeSpan.FromHours(3); 127 128 for (var date = startDate; date <= endDate; date += interval) 129 { 130 data.Add(new { 131 timestamp = date.ToString("yyyy-MM-dd HH:mm:ss"), 132 energyConsumption = Math.Round(random.NextDouble() * 10 + 5, 2) 133 }); 134 } 135 136 return data; 137 } 138 139 // 获取设备电压监控数据 140 [HttpGet] 141 public IActionResult GetDeviceVoltageData(int deviceId, int hours = 24) 142 { 143 _logger.LogInformation($"GetDeviceVoltageData API 被调用,deviceId: {deviceId}, hours: {hours}"); 144 145 // 生成测试电压数据 146 var testData = GenerateTestVoltageData(hours); 147 _logger.LogInformation($"生成的电压测试数据数量: {testData.Count}"); 148 149 return Json(testData); 150 } 151 152 // 获取设备温度监控数据 153 [HttpGet] 154 public IActionResult GetDeviceTemperatureData(int deviceId, int hours = 24) 155 { 156 _logger.LogInformation($"GetDeviceTemperatureData API 被调用,deviceId: {deviceId}, hours: {hours}"); 157 158 // 生成测试温度数据 159 var testData = GenerateTestTemperatureData(hours); 160 _logger.LogInformation($"生成的温度测试数据数量: {testData.Count}"); 161 162 return Json(testData); 163 } 164 165 // 生成测试电压数据 166 private List<object> GenerateTestVoltageData(int hours) 167 { 168 var data = new List<object>(); 169 var random = new Random(); 170 var endTime = DateTime.Now; 171 var startTime = endTime.AddHours(-hours); 172 173 for (var time = startTime; time <= endTime; time += TimeSpan.FromMinutes(30)) 174 { 175 // 模拟220V左右的电压值,带小幅波动 176 double baseVoltage = 220; 177 double fluctuation = (random.NextDouble() - 0.5) * 10; // -5V 到 +5V 的波动 178 double voltage = baseVoltage + fluctuation; 179 180 data.Add(new { 181 timestamp = time.ToString("yyyy-MM-dd HH:mm:ss"), 182 voltage = Math.Round(voltage, 2) 183 }); 184 } 185 186 return data; 187 } 188 189 // 生成测试温度数据 190 private List<object> GenerateTestTemperatureData(int hours) 191 { 192 var data = new List<object>(); 193 var random = new Random(); 194 var endTime = DateTime.Now; 195 var startTime = endTime.AddHours(-hours); 196 197 for (var time = startTime; time <= endTime; time += TimeSpan.FromMinutes(30)) 198 { 199 // 模拟30°C到50°C之间的设备温度 200 double temperature = 30 + random.NextDouble() * 20; 201 202 data.Add(new { 203 timestamp = time.ToString("yyyy-MM-dd HH:mm:ss"), 204 temperature = Math.Round(temperature, 2) 205 }); 206 } 207 208 return data; 209 } 210 211} 212

系统核心架构

  • 后端:.net 9.0,提供设备数据查询、能源趋势计算、状态更新等 API
  • 前端:基于 Razor 视图 + Chart.js,展示能源消耗趋势、设备监控列表、电压 / 温度数据
  • 数据存储:支持 SQLite/MySQL(示例中使用测试数据,可对接真实数据库)

部署步骤

1. 本地发布Asp.net Core 应用
  • 方式 1:命令行发布(进入项目根目录)
1dotnet publish -c Release -o ./publish 2
  • 方式 2:VS 2022 可视化发布
    1. 右键项目 → 发布 → 选择 “文件夹” 目标
    2. 配置发布模式为 “Release”,选择输出路径
    3. 点击 “发布”,生成publish目录(包含应用 DLL、依赖文件)
2. 上传发布文件到 IOT2050
  • 本地终端执行 SCP 命令,将publish目录上传到 IOT2050 的应用目录(示例路径/home/root/energy/):
1scp -r ./publish 用户名@IOT2050IP:/home/root/energy/ 2
3. 配置 systemd 服务(开机自启 + 崩溃重启)

直接运行dotnet 应用.dll仅适合测试,生产环境需用systemd管理服务,确保稳定性。

  • 创建 systemd 服务文件:
1sudo nano /etc/systemd/system/dotnet-energy.service 2
  • 写入服务配置(按需修改路径和应用名):
1[Unit] 2Description=Asp.net Core 能源监控系统 3After=network.target 4 5[Service] 6User=root 7WorkingDirectory=/home/root/energy/publish # 发布目录绝对路径 8ExecStart=/usr/bin/dotnet /home/root/energy/publish/EnergyMonitorDashboard.dll # 应用DLL路径 9Restart=always # 崩溃自动重启 10RestartSec=5 # 重启间隔5秒 11Environment=ASPNETCORE_ENVIRONMENT=Production # 生产环境 12Environment=ASPNETCORE_URLS=http://*:5000 # 监听5000端口 13
  • 启动并设置开机自启:
1# 重新加载systemd配置(识别新服务) 2sudo systemctl daemon-reload 3 4# 启动服务 5sudo systemctl start dotnet-energy.service 6 7# 设置开机自启 8sudo systemctl enable dotnet-energy.service 9 10# 查看服务状态(验证是否启动成功) 11sudo systemctl status dotnet-energy.service 12
4. 服务管理常用命令
1sudo systemctl stop dotnet-energy.service # 停止服务 2sudo systemctl restart dotnet-energy.service # 重启服务 3sudo journalctl -u dotnet-energy.service -f # 实时查看服务日志(排查错误) 4
5. 访问能源监控系统
  • 远程访问:http://IOT2050IP:5000

核心功能说明

  1. 设备状态总览:运行中 / 待机 / 停机设备数量统计卡片
  2. 能源趋势图表:支持今日 / 本周 / 本月数据切换,可视化能源消耗变化
  3. 设备监控列表:显示设备名称、位置、状态、OEE 值,支持启动 / 停机操作
  4. 设备详情页:展示电压、温度实时数据(测试数据可替换为真实设备接口数据)

四、系统整合与优化(可选)

1. Nginx 反向代理(优化Asp.net Core 访问)

为了统一端口访问(避免输入 5000 端口),可配置 Nginx 反向代理Asp.net Core 应用:

  • 编辑 Nginx 配置文件:
1sudo nano /etc/nginx/sites-available/default 2
  • server块中添加反向代理规则:
1location /energy/ { 2 proxy_pass http://localhost:5000/; 3 proxy_set_header Host $host; 4 proxy_set_header X-Real-IP $remote_addr; 5} 6
  • 重启 Nginx:
1sudo systemctl restart nginx 2
  • 访问方式:

http://IOT2050IP:5000即可访问能源监控系统。

  http://IOT2050IP/index.html访问设备监控 HTML 页面。

2. 数据对接真实设备(扩展方向)

  • 设备监控 HTML:修改script中的mockData,替换为 IOT2050 采集的真实设备数据(如通过 MQTT 订阅设备状态)
  • Asp.net Core 系统:将测试数据生成逻辑(GenerateTestTrendData等方法)替换为数据库查询或设备接口调用,对接真实能源、电压、温度数据

五、总结

本文完成了 IOT2050 设备上两大监控系统的完整部署:

  • 静态 HTML 页面:基于 Nginx 快速部署,专注设备状态可视化和工单管理
  • Asp.net Core 应用:基于 systemd 稳定运行,提供能源数据计算和设备控制能力

工业级部署指南:在西门子IOT2050(Debian 12)上搭建.NET 9.0环境与应用部署(进阶篇)》 是转载文章,点击查看原文


上一篇:下一篇:

最近更新


上一篇:下一篇: