工业级部署指南:在西门子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/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
11    <!-- Chart.js -->
12    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/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/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
11    <!-- Chart.js -->
12    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/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. 访问能源监控系统

核心功能说明

  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环境与应用部署(进阶篇)》 是转载文章,点击查看原文


相关推荐


使用前端框架vue做一个小游戏
惜茶2025/11/11

游戏介绍:随机生成4个数字,通过加减乘除,是最后的结果为24。 不足之处: 随机生成的数字可能不能通过运算得出结果24,你们也可以在我的基础上进行修改。我的“确认”按钮每次只能进行两个数的运算。 闲谈:这是我这年暑假做的(挺久的),感觉还不是很成熟。很久没写了,都有些生疏了(^-^) 一、游戏布局 1.1页面布局介绍 不包含标题的情况下,大体上有三个版块: 第一个版块包含了时间、解决问题数、规则第二个版块包含了运算需要的数字和字符第三个版块包含了主要的功能按钮 1.2代码


docker下载配置redis
蓝象_2025/11/9

一、下载redis镜像源创建redis容器 1、创建映射配置文件(如果不手动创建文件,docker run创建的文件会生成文件夹出现错误) mkdir -p /mydata/redis/conf touch /mydata/redis/conf/redis.conf 2、创建redis容器 docker run -p 6379:6379 --name redis \ -v /mydata/redis/data:/data \ -v /mydata/redis/conf/redis


Python 的内置函数 hash
IMPYLH2025/11/7

Python 内建函数列表 > Python 的内置函数 hash Python 的内置函数 hash() 是一个非常有用的工具函数,主要用于获取对象的哈希值。哈希值是一个固定长度的整数,代表该对象的唯一标识。在 Python 中,hash() 函数常用于字典键值、集合元素等场景,因为这些数据结构内部依赖哈希值来快速查找和比较对象。 1. 基本用法 hash() 函数接受一个对象作为参数,返回该对象的哈希值。示例:print(hash("hello")) # 输出字符串 "hello"


从写原生JS到玩转框架:我走过的那些弯路和顿悟时刻
良山有风来2025/11/3

还记得刚入行时,我对着满屏的document.getElementById发誓要征服前端。三年后,当我第一次用Vue在半小时内完成过去需要两天的工作时,我才明白:从前端小白到大佬,差的不是代码量,而是思维模式的彻底转变。 今天,我想和你分享这段旅程中的关键转折点。无论你是正在学习前端的新手,还是已经有一定经验的开发者,相信这些感悟都能帮你少走很多弯路。 从“怎么做”到“做什么”:思维的根本转变 刚学JavaScript时,我的脑子里装满了“怎么做”。比如要做一个待办事项应用,我的思路是这样的:


Redis(95)Redis的防火墙配置如何设置?
Victor3562025/10/31

设置Redis的防火墙配置是确保Redis实例安全的一个关键步骤。正确配置防火墙可以防止未经授权的访问,减少潜在的安全漏洞。以下是如何在不同环境中进行防火墙配置的详细指南。 1. 使用iptables配置防火墙(Linux) 步骤 1: 安装iptables 大多数现代Linux发行版都预装了iptables。如果没有安装,可以使用以下命令进行安装: sudo apt-get install iptables # 在Debian/Ubuntu sudo yum install iptables


MySQL 索引原理
洲覆2025/10/28

文章目录 一、索引1.1 索引分类1.2 主键选择🌟 二、约束2.1 外键约束2.2 约束与索引的区别 三、索引实现原理3.1 索引存储层级结构3.2 B+ 树B+ 树层高问题🌟关于自增 ID 四、索引类型4.1 聚集索引4.2 辅助索引 一、索引 在数据库中,索引是提高查询性能的关键机制。它相当于书籍的目录,通过索引可以快速定位到数据在磁盘中的位置,从而减少 I/O 操作。对于 InnoDB 而言,索引不仅影响查询性能,还决定了数据在物理层的存储结构。 1


Python 的内置函数 delattr
IMPYLH2025/10/25

Python 内建函数列表 > Python 的内置函数 delattr def delattr(obj, name:str): ''' 删除指定的属性 :param obj: 一个对象 :param name: 要删除的属性的名字 ''' Python 的内置函数 delattr 用于动态删除对象的属性。该函数需要两个参数:第一个参数是目标对象,第二个参数是要删除的属性名称(字符串形式)。 示例 运行 class Person: d


c++算法题目总结
July尘2025/10/23

5分题 001 Hello World(输出语句) #include<stdio.h> int main(){ printf("Hello World!"); return 0; } 004计算摄氏温度(简单计算) #include<stdio.h> int main(){ int F; scanf("%d",&F); int C = 5 * (F-32) / 9; printf("Celsius = %d",C);


【自然资源】自然资源系统业务全梳理,点赞收藏
jr4282025/10/22

自然资源系统业务全梳理 结合为自然部门的业务和多方资料来源,对自然资源业务体系和信息化做了梳理,使测绘地理信息行业在自然资源领域如何落地变得具象化。 自然资源管理业务框架的共性特征 分为基础性业务、核心业务与综合性业务3类。 基础性业务包括调查监测,确权登记,主要是摸清家底,确定权属,统一底图底数,由原来部门分治管理时土地、森林等分头调查确权统一到以三调为基础的自然资源一张底图, 测绘和地理信息业务主要是建立自然资源及国土空间数据基准,进行数据采集,并提供信息化技术支撑。 核心业务主要包括所有权


将 EasySQLite 解决方案文件格式从 .sln 升级为更简洁的 .slnx
追逐时光者2025/10/20

前言 EasySQLite 是一个 .NET 9 操作 SQLite 入门到实战详细教程,主要是对学校班级,学生信息进行管理维护。本文的主要内容是将 EasySQLite 项目解决方案文件格式从 .sln 格式升级为更简洁的 .slnx 格式。 GitHub开源地址:github.com/YSGStudyHar… 选型、开发、部署详细教程 第一天、SQLite 简介 第二天、在 Windows 上配置 SQLite环境 第三天、SQLite快速入门 第四天、EasySQLite前后端项目框

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读