Browse Source

fix: add ncloud

工业检测 1 ngày trước cách đây
mục cha
commit
83441d5215
2 tập tin đã thay đổi với 1217 bổ sung0 xóa
  1. 786 0
      docs/1.html
  2. 431 0
      industry-monitor-web/src/lib/ncloud.ts

+ 786 - 0
docs/1.html

@@ -0,0 +1,786 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>工厂振动检测系统 - 总览看板</title>
+  <script src="https://cdn.tailwindcss.com"></script>
+  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
+  <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
+  
+  <!-- Tailwind 配置 -->
+  <script>
+    tailwind.config = {
+      theme: {
+        extend: {
+          colors: {
+            primary: '#165DFF',
+            secondary: '#0A2463',
+            warning: '#FF7D00',
+            danger: '#F53F3F',
+            success: '#00B42A',
+            dark: {
+              100: '#1D2129',
+              200: '#141414',
+              300: '#0A0A0A',
+              400: '#050505'
+            },
+            'neutral-light': '#86909C'
+          },
+          fontFamily: {
+            inter: ['Inter', 'system-ui', 'sans-serif'],
+          },
+        },
+      }
+    }
+  </script>
+  
+  <style type="text/tailwindcss">
+    @layer utilities {
+      .content-auto {
+        content-visibility: auto;
+      }
+      .text-shadow {
+        text-shadow: 0 0 8px rgba(22, 93, 255, 0.5);
+      }
+      .card-hover {
+        transition: all 0.3s ease;
+      }
+      .card-hover:hover {
+        transform: translateY(-5px);
+        box-shadow: 0 12px 20px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(22, 93, 255, 0.5);
+      }
+      .scrollbar-hide::-webkit-scrollbar {
+        display: none;
+      }
+      .scrollbar-hide {
+        -ms-overflow-style: none;
+        scrollbar-width: none;
+      }
+      .animate-pulse-slow {
+        animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+      }
+      .bg-grid {
+        background-image: 
+          linear-gradient(rgba(22, 93, 255, 0.05) 1px, transparent 1px),
+          linear-gradient(90deg, rgba(22, 93, 255, 0.05) 1px, transparent 1px);
+        background-size: 20px 20px;
+      }
+    }
+  </style>
+</head>
+
+<body class="bg-dark-200 text-gray-100 font-inter min-h-screen flex flex-col bg-grid">
+  <div class="flex flex-1 overflow-hidden">
+    <!-- 侧边导航 -->
+    <aside id="sidebar" class="w-64 bg-dark-100 border-r border-dark-300 flex-shrink-0 transition-all duration-300 z-30">
+      <div class="p-4 border-b border-dark-300">
+        <h1 class="text-2xl font-bold text-primary flex items-center">
+          <i class="fa fa-industry mr-3"></i>
+          <span>振动检测系统</span>
+        </h1>
+      </div>
+      
+      <nav class="p-4">
+        <ul class="space-y-1">
+          <li>
+            <a href="#" class="flex items-center px-4 py-3 rounded-lg bg-primary/20 text-primary border-l-4 border-primary">
+              <i class="fa fa-dashboard w-6 text-center"></i>
+              <span>总览看板</span>
+            </a>
+          </li>
+          <li>
+            <a href="#" class="flex items-center px-4 py-3 rounded-lg hover:bg-dark-300 transition-colors">
+              <i class="fa fa-microphone w-6 text-center"></i>
+              <span>设备监控</span>
+            </a>
+          </li>
+          <li>
+            <a href="#" class="flex items-center px-4 py-3 rounded-lg hover:bg-dark-300 transition-colors">
+              <i class="fa fa-bell w-6 text-center"></i>
+              <span>预警管理</span>
+              <span class="ml-auto bg-danger text-white text-xs px-2 py-1 rounded-full">3</span>
+            </a>
+          </li>
+          <li>
+            <a href="#" class="flex items-center px-4 py-3 rounded-lg hover:bg-dark-300 transition-colors">
+              <i class="fa fa-line-chart w-6 text-center"></i>
+              <span>数据分析</span>
+            </a>
+          </li>
+          <li>
+            <a href="#" class="flex items-center px-4 py-3 rounded-lg hover:bg-dark-300 transition-colors">
+              <i class="fa fa-cog w-6 text-center"></i>
+              <span>系统设置</span>
+            </a>
+          </li>
+        </ul>
+      </nav>
+      
+      <div class="absolute bottom-0 w-full p-4 border-t border-dark-300">
+        <div class="flex items-center">
+          <img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-10 h-10 rounded-full border-2 border-primary">
+          <div class="ml-3">
+            <p class="text-sm font-medium">管理员</p>
+            <p class="text-xs text-neutral-light">在线</p>
+          </div>
+        </div>
+      </div>
+    </aside>
+
+    <!-- 主内容区 -->
+    <div class="flex-1 flex flex-col overflow-hidden">
+      <!-- 顶部导航 -->
+      <header class="bg-dark-100 border-b border-dark-300 px-6 py-4 flex items-center justify-between">
+        <div class="flex items-center">
+          <button id="sidebarToggle" class="mr-4 lg:hidden text-gray-300 hover:text-white">
+            <i class="fa fa-bars text-xl"></i>
+          </button>
+          
+          <div class="relative mr-6">
+            <select class="bg-dark-300 border border-dark-400 text-sm rounded-lg px-4 py-2 pr-10 appearance-none focus:outline-none focus:border-primary">
+              <option>主工厂</option>
+              <option>分工厂A</option>
+              <option>分工厂B</option>
+            </select>
+            <i class="fa fa-chevron-down absolute right-3 top-1/2 transform -translate-y-1/2 text-neutral-light"></i>
+          </div>
+        </div>
+        
+        <div class="flex items-center space-x-4">
+          <div id="currentTime" class="text-sm text-neutral-light"></div>
+          <button class="relative p-2 text-gray-300 hover:text-white">
+            <i class="fa fa-bell text-xl"></i>
+            <span class="absolute top-1 right-1 w-2 h-2 bg-danger rounded-full"></span>
+          </button>
+          <button class="p-2 text-gray-300 hover:text-white">
+            <i class="fa fa-cog text-xl"></i>
+          </button>
+        </div>
+      </header>
+
+      <!-- 内容滚动区 -->
+      <main class="flex-1 overflow-y-auto p-6 scrollbar-hide">
+        <div class="max-w-7xl mx-auto">
+          <!-- 页面标题 -->
+          <div class="mb-6">
+            <h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-white">工厂振动检测总览</h2>
+            <p class="text-neutral-light mt-1">实时监控全厂设备振动状态与关键指标</p>
+          </div>
+          
+          <!-- 核心指标卡片 -->
+          <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300">
+              <div class="flex justify-between items-start">
+                <div>
+                  <p class="text-neutral-light text-sm">运行设备</p>
+                  <h3 class="text-3xl font-bold mt-1">186 <span class="text-sm font-normal text-success ml-2"><i class="fa fa-arrow-up"></i> 5%</span></h3>
+                </div>
+                <div class="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center text-primary">
+                  <i class="fa fa-machine text-xl"></i>
+                </div>
+              </div>
+              <div class="mt-4 text-xs text-neutral-light">
+                总设备数: 200台
+              </div>
+            </div>
+            
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300">
+              <div class="flex justify-between items-start">
+                <div>
+                  <p class="text-neutral-light text-sm">今日预警</p>
+                  <h3 class="text-3xl font-bold mt-1">12 <span class="text-sm font-normal text-danger ml-2"><i class="fa fa-arrow-up"></i> 2</span></h3>
+                </div>
+                <div class="w-12 h-12 rounded-full bg-warning/20 flex items-center justify-center text-warning">
+                  <i class="fa fa-exclamation-triangle text-xl"></i>
+                </div>
+              </div>
+              <div class="mt-4 text-xs text-neutral-light">
+                高风险: 2, 中风险: 5, 低风险: 5
+              </div>
+            </div>
+            
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300">
+              <div class="flex justify-between items-start">
+                <div>
+                  <p class="text-neutral-light text-sm">设备健康指数</p>
+                  <h3 class="text-3xl font-bold mt-1">78 <span class="text-sm font-normal text-success ml-2"><i class="fa fa-arrow-up"></i> 3%</span></h3>
+                </div>
+                <div class="w-12 h-12 rounded-full bg-success/20 flex items-center justify-center text-success">
+                  <i class="fa fa-heartbeat text-xl"></i>
+                </div>
+              </div>
+              <div class="mt-4">
+                <div class="w-full bg-dark-300 rounded-full h-2">
+                  <div class="bg-gradient-to-r from-success to-primary h-2 rounded-full" style="width: 78%"></div>
+                </div>
+              </div>
+            </div>
+            
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300">
+              <div class="flex justify-between items-start">
+                <div>
+                  <p class="text-neutral-light text-sm">平均振动值</p>
+                  <h3 class="text-3xl font-bold mt-1">18.5 <span class="text-sm font-normal text-danger ml-2"><i class="fa fa-arrow-up"></i> 1.2</span> mm/s</h3>
+                </div>
+                <div class="w-12 h-12 rounded-full bg-danger/20 flex items-center justify-center text-danger">
+                  <i class="fa fa-tachometer text-xl"></i>
+                </div>
+              </div>
+              <div class="mt-4 text-xs text-neutral-light">
+                阈值: 25 mm/s
+              </div>
+            </div>
+          </div>
+          
+          <!-- 图表区域 -->
+          <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
+            <!-- 设备状态分布饼图 -->
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300 lg:col-span-1">
+              <div class="flex justify-between items-center mb-6">
+                <h3 class="font-semibold text-lg">设备状态分布</h3>
+                <div class="text-xs text-neutral-light">更新于: <span id="updateTime">10:24:36</span></div>
+              </div>
+              <div class="h-64">
+                <div id="statusPieChart" class="w-full h-full"></div>
+              </div>
+              <div class="mt-6 grid grid-cols-2 gap-4">
+                <div class="flex items-center">
+                  <span class="w-3 h-3 rounded-full bg-success mr-2"></span>
+                  <span class="text-sm">正常: 168台</span>
+                </div>
+                <div class="flex items-center">
+                  <span class="w-3 h-3 rounded-full bg-warning mr-2"></span>
+                  <span class="text-sm">警告: 28台</span>
+                </div>
+                <div class="flex items-center">
+                  <span class="w-3 h-3 rounded-full bg-danger mr-2"></span>
+                  <span class="text-sm">危险: 4台</span>
+                </div>
+                <div class="flex items-center">
+                  <span class="w-3 h-3 rounded-full bg-neutral-light/50 mr-2"></span>
+                  <span class="text-sm">停机: 0台</span>
+                </div>
+              </div>
+            </div>
+            
+            <!-- 健康指数柱状图 -->
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300 lg:col-span-2">
+              <div class="flex justify-between items-center mb-6">
+                <h3 class="font-semibold text-lg">车间健康指数</h3>
+                <div class="flex space-x-2">
+                  <button class="px-3 py-1 text-xs bg-primary/20 text-primary rounded-lg">今日</button>
+                  <button class="px-3 py-1 text-xs bg-dark-300 text-neutral-light rounded-lg hover:bg-dark-400">本周</button>
+                  <button class="px-3 py-1 text-xs bg-dark-300 text-neutral-light rounded-lg hover:bg-dark-400">本月</button>
+                </div>
+              </div>
+              <div class="h-64">
+                <div id="healthBarChart" class="w-full h-full"></div>
+              </div>
+            </div>
+          </div>
+          
+          <!-- 设备地图与预警列表 -->
+          <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
+            <!-- 设备地图视图 -->
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300 lg:col-span-2">
+              <div class="flex justify-between items-center mb-6">
+                <h3 class="font-semibold text-lg">设备位置热力图</h3>
+                <div class="text-xs text-neutral-light">刷新: <button class="text-primary hover:underline">手动</button></div>
+              </div>
+              <div class="h-80 relative bg-dark-300 rounded-lg overflow-hidden">
+                <!-- 工厂地图背景 -->
+                <img src="https://picsum.photos/id/1029/800/400" alt="工厂地图" class="w-full h-full object-cover opacity-20">
+                
+                <!-- 设备位置标记 - 动态生成 -->
+                <div class="absolute top-1/4 left-1/3" data-vibration="12">
+                  <div class="w-4 h-4 rounded-full bg-success animate-pulse-slow border-2 border-success/50 cursor-pointer" title="设备A-01: 正常 (12.5 mm/s)"></div>
+                </div>
+                <div class="absolute top-1/3 right-1/4" data-vibration="22">
+                  <div class="w-4 h-4 rounded-full bg-warning animate-pulse-slow border-2 border-warning/50 cursor-pointer" title="设备B-05: 警告 (22.3 mm/s)"></div>
+                </div>
+                <div class="absolute bottom-1/3 left-1/5" data-vibration="35">
+                  <div class="w-4 h-4 rounded-full bg-danger animate-pulse-slow border-2 border-danger/50 cursor-pointer" title="设备C-02: 危险 (35.1 mm/s)"></div>
+                </div>
+                <div class="absolute top-2/3 right-1/3" data-vibration="18">
+                  <div class="w-4 h-4 rounded-full bg-success animate-pulse-slow border-2 border-success/50 cursor-pointer" title="设备D-10: 正常 (18.2 mm/s)"></div>
+                </div>
+                
+                <!-- 图例 -->
+                <div class="absolute bottom-4 right-4 bg-dark-100/80 backdrop-blur-sm p-3 rounded-lg border border-dark-300">
+                  <div class="flex items-center mb-2">
+                    <span class="w-3 h-3 rounded-full bg-success mr-2"></span>
+                    <span class="text-xs">正常 &lt;20</span>
+                  </div>
+                  <div class="flex items-center mb-2">
+                    <span class="w-3 h-3 rounded-full bg-warning mr-2"></span>
+                    <span class="text-xs">警告 20-25</span>
+                  </div>
+                  <div class="flex items-center">
+                    <span class="w-3 h-3 rounded-full bg-danger mr-2"></span>
+                    <span class="text-xs">危险 &gt;25</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+            
+            <!-- 实时警报列表 -->
+            <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300">
+              <div class="flex justify-between items-center mb-6">
+                <h3 class="font-semibold text-lg">实时警报</h3>
+                <span class="text-xs bg-danger/20 text-danger px-2 py-1 rounded-full">紧急</span>
+              </div>
+              <div class="space-y-4 max-h-80 overflow-y-auto pr-2 scrollbar-hide">
+                <!-- 警报项1 - 危险 -->
+                <div class="bg-dark-300/50 border-l-4 border-danger p-4 rounded-lg">
+                  <div class="flex justify-between items-start">
+                    <div>
+                      <h4 class="font-medium text-sm">设备C-02振动异常</h4>
+                      <p class="text-xs text-neutral-light mt-1">振动值: 35.1 mm/s (阈值: 25 mm/s)</p>
+                    </div>
+                    <span class="text-xs text-danger">10:22</span>
+                  </div>
+                  <div class="mt-3 flex justify-between items-center">
+                    <span class="text-xs bg-danger/20 text-danger px-2 py-1 rounded-full">高风险</span>
+                    <button class="text-xs text-primary hover:underline">处理</button>
+                  </div>
+                </div>
+                
+                <!-- 警报项2 - 警告 -->
+                <div class="bg-dark-300/50 border-l-4 border-warning p-4 rounded-lg">
+                  <div class="flex justify-between items-start">
+                    <div>
+                      <h4 class="font-medium text-sm">设备B-05振动升高</h4>
+                      <p class="text-xs text-neutral-light mt-1">振动值: 22.3 mm/s (阈值: 25 mm/s)</p>
+                    </div>
+                    <span class="text-xs text-warning">10:15</span>
+                  </div>
+                  <div class="mt-3 flex justify-between items-center">
+                    <span class="text-xs bg-warning/20 text-warning px-2 py-1 rounded-full">中风险</span>
+                    <button class="text-xs text-primary hover:underline">确认</button>
+                  </div>
+                </div>
+                
+                <!-- 警报项3 - 警告 -->
+                <div class="bg-dark-300/50 border-l-4 border-warning p-4 rounded-lg">
+                  <div class="flex justify-between items-start">
+                    <div>
+                      <h4 class="font-medium text-sm">设备E-08轴承振动异常</h4>
+                      <p class="text-xs text-neutral-light mt-1">振动值: 21.8 mm/s (阈值: 25 mm/s)</p>
+                    </div>
+                    <span class="text-xs text-warning">10:08</span>
+                  </div>
+                  <div class="mt-3 flex justify-between items-center">
+                    <span class="text-xs bg-warning/20 text-warning px-2 py-1 rounded-full">中风险</span>
+                    <button class="text-xs text-primary hover:underline">确认</button>
+                  </div>
+                </div>
+                
+                <!-- 警报项4 - 正常 -->
+                <div class="bg-dark-300/50 border-l-4 border-success p-4 rounded-lg">
+                  <div class="flex justify-between items-start">
+                    <div>
+                      <h4 class="font-medium text-sm">设备A-01恢复正常</h4>
+                      <p class="text-xs text-neutral-light mt-1">振动值: 12.5 mm/s (阈值: 25 mm/s)</p>
+                    </div>
+                    <span class="text-xs text-success">09:55</span>
+                  </div>
+                  <div class="mt-3 flex justify-between items-center">
+                    <span class="text-xs bg-success/20 text-success px-2 py-1 rounded-full">已恢复</span>
+                    <button class="text-xs text-primary hover:underline">已处理</button>
+                  </div>
+                </div>
+              </div>
+              <button class="w-full mt-4 text-sm text-primary hover:underline flex items-center justify-center">
+                查看全部警报 <i class="fa fa-angle-right ml-2"></i>
+              </button>
+            </div>
+          </div>
+          
+          <!-- 健康指数趋势图 -->
+          <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300 mb-8">
+            <div class="flex justify-between items-center mb-6">
+              <h3 class="font-semibold text-lg">健康指数趋势</h3>
+              <div class="flex space-x-2">
+                <button class="px-3 py-1 text-xs bg-primary/20 text-primary rounded-lg">24小时</button>
+                <button class="px-3 py-1 text-xs bg-dark-300 text-neutral-light rounded-lg hover:bg-dark-400">7天</button>
+                <button class="px-3 py-1 text-xs bg-dark-300 text-neutral-light rounded-lg hover:bg-dark-400">30天</button>
+              </div>
+            </div>
+            <div class="h-64">
+              <div id="trendLineChart" class="w-full h-full"></div>
+            </div>
+          </div>
+          
+          <!-- 关键设备监控 -->
+          <div class="bg-dark-100 rounded-xl p-6 card-hover border border-dark-300">
+            <div class="flex justify-between items-center mb-6">
+              <h3 class="font-semibold text-lg">关键设备监控</h3>
+              <div class="relative">
+                <select class="bg-dark-300 border border-dark-400 text-sm rounded-lg px-4 py-2 pr-10 appearance-none focus:outline-none focus:border-primary">
+                  <option>全部车间</option>
+                  <option>装配车间</option>
+                  <option>加工车间</option>
+                  <option>涂装车间</option>
+                </select>
+                <i class="fa fa-chevron-down absolute right-3 top-1/2 transform -translate-y-1/2 text-neutral-light"></i>
+              </div>
+            </div>
+            
+            <div class="overflow-x-auto">
+              <table class="min-w-full">
+                <thead>
+                  <tr class="border-b border-dark-300">
+                    <th class="text-left py-3 px-4 text-xs font-medium text-neutral-light">设备编号</th>
+                    <th class="text-left py-3 px-4 text-xs font-medium text-neutral-light">设备名称</th>
+                    <th class="text-left py-3 px-4 text-xs font-medium text-neutral-light">车间</th>
+                    <th class="text-left py-3 px-4 text-xs font-medium text-neutral-light">振动值 (mm/s)</th>
+                    <th class="text-left py-3 px-4 text-xs font-medium text-neutral-light">健康状态</th>
+                    <th class="text-left py-3 px-4 text-xs font-medium text-neutral-light">操作</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <!-- 设备行1 - 危险 -->
+                  <tr class="border-b border-dark-300 hover:bg-dark-300/50 transition-colors">
+                    <td class="py-3 px-4 text-sm">C-02</td>
+                    <td class="py-3 px-4 text-sm">主轴电机</td>
+                    <td class="py-3 px-4 text-sm">加工车间</td>
+                    <td class="py-3 px-4 text-sm">35.1 <span class="text-danger ml-1"><i class="fa fa-arrow-up"></i></span></td>
+                    <td class="py-3 px-4">
+                      <span class="px-2 py-1 text-xs rounded-full bg-danger/20 text-danger">危险</span>
+                    </td>
+                    <td class="py-3 px-4">
+                      <button class="text-primary hover:underline text-sm">详情</button>
+                    </td>
+                  </tr>
+                  
+                  <!-- 设备行2 - 警告 -->
+                  <tr class="border-b border-dark-300 hover:bg-dark-300/50 transition-colors">
+                    <td class="py-3 px-4 text-sm">B-05</td>
+                    <td class="py-3 px-4 text-sm">传送带电机</td>
+                    <td class="py-3 px-4 text-sm">装配车间</td>
+                    <td class="py-3 px-4 text-sm">22.3 <span class="text-warning ml-1"><i class="fa fa-arrow-up"></i></span></td>
+                    <td class="py-3 px-4">
+                      <span class="px-2 py-1 text-xs rounded-full bg-warning/20 text-warning">警告</span>
+                    </td>
+                    <td class="py-3 px-4">
+                      <button class="text-primary hover:underline text-sm">详情</button>
+                    </td>
+                  </tr>
+                  
+                  <!-- 设备行3 - 正常 -->
+                  <tr class="border-b border-dark-300 hover:bg-dark-300/50 transition-colors">
+                    <td class="py-3 px-4 text-sm">A-01</td>
+                    <td class="py-3 px-4 text-sm">冲压机</td>
+                    <td class="py-3 px-4 text-sm">加工车间</td>
+                    <td class="py-3 px-4 text-sm">12.5 <span class="text-success ml-1"><i class="fa fa-arrow-down"></i></span></td>
+                    <td class="py-3 px-4">
+                      <span class="px-2 py-1 text-xs rounded-full bg-success/20 text-success">正常</span>
+                    </td>
+                    <td class="py-3 px-4">
+                      <button class="text-primary hover:underline text-sm">详情</button>
+                    </td>
+                  </tr>
+                  
+                  <!-- 设备行4 - 正常 -->
+                  <tr class="border-b border-dark-300 hover:bg-dark-300/50 transition-colors">
+                    <td class="py-3 px-4 text-sm">D-10</td>
+                    <td class="py-3 px-4 text-sm">烘干机</td>
+                    <td class="py-3 px-4 text-sm">涂装车间</td>
+                    <td class="py-3 px-4 text-sm">18.2 <span class="text-neutral-light ml-1"><i class="fa fa-minus"></i></span></td>
+                    <td class="py-3 px-4">
+                      <span class="px-2 py-1 text-xs rounded-full bg-success/20 text-success">正常</span>
+                    </td>
+                    <td class="py-3 px-4">
+                      <button class="text-primary hover:underline text-sm">详情</button>
+                    </td>
+                  </tr>
+                  
+                  <!-- 设备行5 - 正常 -->
+                  <tr class="hover:bg-dark-300/50 transition-colors">
+                    <td class="py-3 px-4 text-sm">E-08</td>
+                    <td class="py-3 px-4 text-sm">风机</td>
+                    <td class="py-3 px-4 text-sm">装配车间</td>
+                    <td class="py-3 px-4 text-sm">21.8 <span class="text-warning ml-1"><i class="fa fa-arrow-up"></i></span></td>
+                    <td class="py-3 px-4">
+                      <span class="px-2 py-1 text-xs rounded-full bg-warning/20 text-warning">警告</span>
+                    </td>
+                    <td class="py-3 px-4">
+                      <button class="text-primary hover:underline text-sm">详情</button>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
+            
+            <button class="mt-6 w-full py-2 border border-primary text-primary rounded-lg hover:bg-primary/10 transition-colors">
+              加载更多设备
+            </button>
+          </div>
+        </div>
+      </main>
+    </div>
+  </div>
+
+  <script>
+    // 更新当前时间
+    function updateTime() {
+      const now = new Date();
+      const timeString = now.toLocaleTimeString('zh-CN', {
+        hour: '2-digit',
+        minute: '2-digit',
+        second: '2-digit'
+      });
+      document.getElementById('currentTime').textContent = timeString;
+      document.getElementById('updateTime').textContent = timeString;
+    }
+    
+    setInterval(updateTime, 1000);
+    updateTime(); // 初始调用
+    
+    // 侧边栏切换
+    document.getElementById('sidebarToggle').addEventListener('click', function() {
+      const sidebar = document.getElementById('sidebar');
+      sidebar.classList.toggle('translate-x-0');
+      sidebar.classList.toggle('-translate-x-full');
+    });
+    
+    // ECharts 图表初始化
+    document.addEventListener('DOMContentLoaded', function() {
+      // 设备状态分布饼图
+      const statusPieChart = echarts.init(document.getElementById('statusPieChart'));
+      const statusPieOption = {
+        tooltip: {
+          trigger: 'item',
+          formatter: '{a} <br/>{b}: {c} ({d}%)'
+        },
+        series: [
+          {
+            name: '设备状态',
+            type: 'pie',
+            radius: ['40%', '70%'],
+            avoidLabelOverlap: false,
+            itemStyle: {
+              borderRadius: 10,
+              borderColor: '#1D2129',
+              borderWidth: 1
+            },
+            label: {
+              show: false,
+              position: 'center'
+            },
+            emphasis: {
+              label: {
+                show: true,
+                fontSize: '16',
+                fontWeight: 'bold'
+              }
+            },
+            labelLine: {
+              show: false
+            },
+            data: [
+              {value: 168, name: '正常', itemStyle: {color: '#00B42A'}},
+              {value: 28, name: '警告', itemStyle: {color: '#FF7D00'}},
+              {value: 4, name: '危险', itemStyle: {color: '#F53F3F'}},
+              {value: 0, name: '停机', itemStyle: {color: '#86909C'}}
+            ]
+          }
+        ]
+      };
+      statusPieChart.setOption(statusPieOption);
+      
+      // 车间健康指数柱状图
+      const healthBarChart = echarts.init(document.getElementById('healthBarChart'));
+      const healthBarOption = {
+        tooltip: {
+          trigger: 'axis'
+        },
+        xAxis: {
+          type: 'category',
+          data: ['装配车间', '加工车间', '涂装车间', '仓储车间', '动力车间'],
+          axisTick: {
+            alignWithLabel: true
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#333'
+            }
+          }
+        },
+        yAxis: {
+          type: 'value',
+          min: 0,
+          max: 100,
+          splitLine: {
+            lineStyle: {
+              color: '#333'
+            }
+          }
+        },
+        series: [
+          {
+            name: '健康指数',
+            type: 'bar',
+            barWidth: '60%',
+            data: [85, 68, 92, 76, 88],
+            itemStyle: {
+              color: function(params) {
+                const colorList = ['#165DFF', '#165DFF', '#165DFF', '#165DFF', '#165DFF'];
+                if (params.data < 70) colorList[params.dataIndex] = '#F53F3F';
+                else if (params.data < 80) colorList[params.dataIndex] = '#FF7D00';
+                return colorList[params.dataIndex];
+              },
+              borderRadius: 4
+            }
+          }
+        ]
+      };
+      healthBarChart.setOption(healthBarOption);
+      
+      // 健康指数趋势图
+      const trendLineChart = echarts.init(document.getElementById('trendLineChart'));
+      const trendLineOption = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'cross'
+          }
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: Array(24).fill('').map((_, i) => i + '时'),
+          axisLine: {
+            lineStyle: {
+              color: '#333'
+            }
+          }
+        },
+        yAxis: [
+          {
+            type: 'value',
+            name: '健康指数',
+            min: 0,
+            max: 100,
+            interval: 20,
+            axisLine: {
+              lineStyle: {
+                color: '#165DFF'
+              }
+            },
+            splitLine: {
+              lineStyle: {
+                color: '#333'
+              }
+            },
+            axisLabel: {
+              textStyle: {
+                color: '#165DFF'
+              }
+            }
+          },
+          {
+            type: 'value',
+            name: '振动值 (mm/s)',
+            min: 0,
+            max: 40,
+            interval: 10,
+            axisLine: {
+              lineStyle: {
+                color: '#F53F3F'
+              }
+            },
+            splitLine: {
+              show: false
+            },
+            axisLabel: {
+              textStyle: {
+                color: '#F53F3F'
+              }
+            }
+          }
+        ],
+        series: [
+          {
+            name: '健康指数',
+            type: 'line',
+            yAxisIndex: 0,
+            data: [75, 76, 77, 78, 76, 75, 74, 73, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 81, 80, 79, 78, 77],
+            symbolSize: 6,
+            itemStyle: {
+              color: '#165DFF'
+            },
+            lineStyle: {
+              color: '#165DFF'
+            },
+            areaStyle: {
+              color: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 0,
+                y2: 1,
+                colorStops: [{
+                  offset: 0,
+                  color: 'rgba(22, 93, 255, 0.5)'
+                }, {
+                  offset: 1,
+                  color: 'rgba(22, 93, 255, 0.05)'
+                }]
+              }
+            }
+          },
+          {
+            name: '振动值',
+            type: 'line',
+            yAxisIndex: 1,
+            data: [18, 17, 16, 15, 17, 19, 21, 22, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 15, 16, 17, 18, 19, 20],
+            symbolSize: 6,
+            itemStyle: {
+              color: '#F53F3F'
+            },
+            lineStyle: {
+              color: '#F53F3F'
+            },
+            areaStyle: {
+              color: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 0,
+                y2: 1,
+                colorStops: [{
+                  offset: 0,
+                  color: 'rgba(245, 63, 63, 0.5)'
+                }, {
+                  offset: 1,
+                  color: 'rgba(245, 63, 63, 0.05)'
+                }]
+              }
+            }
+          }
+        ]
+      };
+      trendLineChart.setOption(trendLineOption);
+      
+      // 窗口大小改变时重新渲染图表
+      window.addEventListener('resize', function() {
+        statusPieChart.resize();
+        healthBarChart.resize();
+        trendLineChart.resize();
+      });
+    });
+    
+    // 设备地图标记交互效果
+    document.querySelectorAll('[data-vibration]').forEach(marker => {
+      marker.addEventListener('mouseenter', function() {
+        this.querySelector('div').classList.add('scale-150');
+      });
+      marker.addEventListener('mouseleave', function() {
+        this.querySelector('div').classList.remove('scale-150');
+      });
+    });
+  </script>
+</body>
+</html>

+ 431 - 0
industry-monitor-web/src/lib/ncloud.ts

@@ -0,0 +1,431 @@
+// CloudObject.ts
+
+let serverURL = `https://dev.fmode.cn/parse`;
+if (location.protocol == "http:") {
+    serverURL = `http://dev.fmode.cn:1337/parse`;
+}
+
+export class CloudObject {
+    className: string;
+    id: string | undefined = undefined;
+    createdAt: any;
+    updatedAt: any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = serverURL + `/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(serverURL + `/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = undefined;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = { where: {} };
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds: string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
+        this.queryParams["where"][key] = value;
+    }
+
+    async get(id: string) {
+        const url = serverURL + `/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        if (json) {
+            let existsObject = this.dataToObj(json)
+            return existsObject;
+        }
+        return null
+    }
+
+    async find(): Promise<Array<CloudObject>> {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key => {
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if (key == "include") {
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if (queryStr) {
+                url += `${key}=${paramStr}`;
+            } else {
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item: any) => this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}&limit=1`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists: any): CloudObject {
+        let existsObject = new CloudObject(this.className);
+        Object.keys(exists).forEach(key => {
+            if (exists[key]?.__type == "Object") {
+                exists[key] = this.dataToObj(exists[key])
+            }
+        })
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if (userCacheStr) {
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken: string | null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(serverURL + `/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string): Promise<CloudUser | null> {
+        const response = await fetch(serverURL + `/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(serverURL + `/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        let result = await response?.json();
+
+        if (result?.error) {
+            console.error(result?.error);
+            if (result?.error == "Invalid session token") {
+                this.clearUserCache()
+                return true;
+            }
+            return false;
+        }
+
+        this.clearUserCache()
+        return true;
+    }
+    clearUserCache() {
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = undefined;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(serverURL + `/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
+
+    override async save() {
+        let method = "POST";
+        let url = serverURL + `/users`;
+
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        let data: any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions: any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(this.data))
+        return this;
+    }
+}
+
+export class CloudApi {
+    async fetch(path: string, body: any, options?: {
+        method: string
+        body: any
+    }) {
+
+        let reqOpts: any = {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            method: options?.method || "POST",
+            mode: "cors",
+            credentials: "omit"
+        }
+        if (body || options?.body) {
+            reqOpts.body = JSON.stringify(body || options?.body);
+            reqOpts.json = true;
+        }
+        let host = `https://dev.fmode.cn`
+        // host = `http://127.0.0.1:1337`
+        let url = `${host}/api/` + path
+        console.log(url, reqOpts)
+        const response = await fetch(url, reqOpts);
+        let json = await response.json();
+        return json
+    }
+}