From 2e7dbd705fc82e8db74b073e55af938d67d8c19f Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 17 三月 2026 09:05:49 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/dashboard/dashboard.html |  308 ++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 252 insertions(+), 56 deletions(-)

diff --git a/src/main/webapp/views/dashboard/dashboard.html b/src/main/webapp/views/dashboard/dashboard.html
index 857578d..3c2b87e 100644
--- a/src/main/webapp/views/dashboard/dashboard.html
+++ b/src/main/webapp/views/dashboard/dashboard.html
@@ -271,6 +271,14 @@
       font-weight: 700;
     }
 
+    .panel-actions {
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      flex-wrap: wrap;
+      gap: 8px;
+    }
+
     .mini-grid {
       display: grid;
       grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -324,6 +332,26 @@
       border-color: rgba(151, 110, 204, 0.18);
     }
 
+    .network-mini-ok {
+      background: linear-gradient(180deg, rgba(47, 163, 142, 0.11) 0%, rgba(47, 163, 142, 0.03) 100%);
+      border-color: rgba(47, 163, 142, 0.18);
+    }
+
+    .network-mini-warning {
+      background: linear-gradient(180deg, rgba(245, 154, 74, 0.12) 0%, rgba(245, 154, 74, 0.03) 100%);
+      border-color: rgba(245, 154, 74, 0.18);
+    }
+
+    .network-mini-offline {
+      background: linear-gradient(180deg, rgba(222, 92, 92, 0.10) 0%, rgba(222, 92, 92, 0.03) 100%);
+      border-color: rgba(222, 92, 92, 0.18);
+    }
+
+    .network-mini-latency {
+      background: linear-gradient(180deg, rgba(31, 111, 178, 0.09) 0%, rgba(31, 111, 178, 0.02) 100%);
+      border-color: rgba(31, 111, 178, 0.16);
+    }
+
     .chart-grid {
       display: grid;
       grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -344,12 +372,20 @@
       margin-bottom: 8px;
     }
 
+    .chart-subtitle {
+      margin-bottom: 10px;
+      font-size: 12px;
+      color: #7d90a4;
+      line-height: 1.6;
+    }
+
     .chart-box {
       width: 100%;
       height: 280px;
     }
 
     .panel-device .mini-grid,
+    .panel-network .mini-grid,
     .panel-ai .mini-grid {
       grid-template-columns: repeat(2, minmax(0, 1fr));
     }
@@ -387,6 +423,7 @@
     }
 
     .device-chart-box,
+    .network-chart-box,
     .ai-chart-box {
       width: 100%;
       height: 250px;
@@ -466,8 +503,80 @@
       padding: 8px 10px;
     }
 
+    .network-note {
+      margin-top: 6px;
+      font-size: 12px;
+      line-height: 1.5;
+      border-radius: 12px;
+      padding: 8px 10px;
+    }
+
+    .network-note-danger {
+      color: #c15b5b;
+      background: rgba(222, 92, 92, 0.08);
+    }
+
+    .network-note-warning {
+      color: #b67632;
+      background: rgba(245, 154, 74, 0.12);
+    }
+
+    .network-note-info {
+      color: #657d95;
+      background: rgba(125, 144, 164, 0.10);
+    }
+
+    .network-healthy-state {
+      margin-top: 14px;
+      padding: 14px 16px;
+      border-radius: 16px;
+      border: 1px solid rgba(87, 186, 128, 0.20);
+      background: linear-gradient(135deg, rgba(87, 186, 128, 0.10) 0%, rgba(87, 186, 128, 0.03) 100%);
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 14px;
+    }
+
+    .network-healthy-main {
+      min-width: 0;
+      flex: 1;
+    }
+
+    .network-healthy-title {
+      font-size: 14px;
+      font-weight: 700;
+      color: #2d7f56;
+      line-height: 1.5;
+    }
+
+    .network-healthy-desc {
+      margin-top: 4px;
+      font-size: 12px;
+      color: #698399;
+      line-height: 1.6;
+    }
+
+    .network-healthy-tags {
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      flex-wrap: wrap;
+      gap: 8px;
+    }
+
+    .network-healthy-tag {
+      padding: 6px 10px;
+      border-radius: 999px;
+      background: rgba(255, 255, 255, 0.72);
+      border: 1px solid rgba(87, 186, 128, 0.18);
+      font-size: 12px;
+      color: #557160;
+      white-space: nowrap;
+    }
+
     .recent-panel {
-      min-height: 100%;
+      min-height: 0;
     }
 
     .recent-table {
@@ -553,6 +662,7 @@
       }
 
       .panel-device .mini-grid,
+      .panel-network .mini-grid,
       .panel-ai .mini-grid {
         grid-template-columns: 1fr;
       }
@@ -579,6 +689,20 @@
       .route-row-side {
         align-items: flex-start;
         text-align: left;
+      }
+
+      .network-healthy-state {
+        flex-direction: column;
+        align-items: flex-start;
+      }
+
+      .network-healthy-tags {
+        justify-content: flex-start;
+      }
+
+      .panel-actions {
+        width: 100%;
+        justify-content: flex-start;
       }
     }
   </style>
@@ -732,60 +856,6 @@
           </el-table>
         </div>
       </section>
-    </div>
-
-    <div class="dashboard-column">
-      <section class="panel panel-device">
-        <div class="panel-header">
-          <div>
-            <div class="panel-kicker">Devices</div>
-            <h2 class="panel-title">璁惧鎬佸娍</h2>
-            <div class="panel-desc">姹囨�昏緭閫佺珯鐐广�佸爢鍨涙満銆佸弻宸ヤ綅鍫嗗灈鏈轰笌 RGV 鐨勫湪绾裤�佸繖纰屽拰鍛婅鎯呭喌銆�</div>
-          </div>
-          <el-tag size="small" type="info">鍦ㄧ嚎鐜� {{ devices.overview.onlineRate || 0 }}%</el-tag>
-        </div>
-
-        <div class="mini-grid">
-          <div class="mini-card">
-            <div class="mini-label">璁惧鎬绘暟</div>
-            <div class="mini-value">{{ formatNumber(devices.overview.total) }}</div>
-            <div class="mini-hint">宸插惎鐢ㄩ厤缃澶�</div>
-          </div>
-          <div class="mini-card">
-            <div class="mini-label">鍦ㄧ嚎璁惧</div>
-            <div class="mini-value">{{ formatNumber(devices.overview.online) }}</div>
-            <div class="mini-hint">瀹炴椂杩為�氳澶囨暟閲�</div>
-          </div>
-          <div class="mini-card">
-            <div class="mini-label">蹇欑璁惧</div>
-            <div class="mini-value">{{ formatNumber(devices.overview.busy) }}</div>
-            <div class="mini-hint">褰撳墠鎵胯浇浠诲姟鐨勮澶�</div>
-          </div>
-          <div class="mini-card">
-            <div class="mini-label">鍛婅璁惧</div>
-            <div class="mini-value">{{ formatNumber(devices.overview.alarm) }}</div>
-            <div class="mini-hint">鍚樆濉炴垨鎶ヨ鐘舵��</div>
-          </div>
-        </div>
-
-        <div class="chart-card">
-          <div class="chart-title">璁惧鍦ㄧ嚎鍒嗗竷</div>
-          <div ref="deviceTypeChart" class="device-chart-box"></div>
-        </div>
-
-        <div class="type-list">
-          <div v-for="item in devices.typeStats" :key="item.name" class="type-row">
-            <div class="type-row-main">
-              <div class="type-row-name">{{ item.name }}</div>
-              <div class="type-row-desc">鍦ㄧ嚎 {{ formatNumber(item.online) }} / 鎬绘暟 {{ formatNumber(item.total) }}锛岀绾� {{ formatNumber(item.offline) }}</div>
-            </div>
-            <div class="type-row-side">
-              <el-tag size="mini" type="success">蹇欑 {{ formatNumber(item.busy) }}</el-tag>
-              <el-tag size="mini" :type="item.alarm > 0 ? 'danger' : 'info'">鍛婅 {{ formatNumber(item.alarm) }}</el-tag>
-            </div>
-          </div>
-        </div>
-      </section>
 
       <section class="panel panel-ai">
         <div class="panel-header">
@@ -842,6 +912,132 @@
         <el-empty v-else description="鏆傛棤 AI 璺敱鏁版嵁"></el-empty>
       </section>
     </div>
+
+    <div class="dashboard-column">
+      <section class="panel panel-device">
+        <div class="panel-header">
+          <div>
+            <div class="panel-kicker">Devices</div>
+            <h2 class="panel-title">璁惧鎬佸娍</h2>
+            <div class="panel-desc">姹囨�昏緭閫佺珯鐐广�佸爢鍨涙満銆佸弻宸ヤ綅鍫嗗灈鏈轰笌 RGV 鐨勫湪绾裤�佸繖纰屽拰鍛婅鎯呭喌銆�</div>
+          </div>
+          <el-tag size="small" type="info">鍦ㄧ嚎鐜� {{ devices.overview.onlineRate || 0 }}%</el-tag>
+        </div>
+
+        <div class="mini-grid">
+          <div class="mini-card">
+            <div class="mini-label">璁惧鎬绘暟</div>
+            <div class="mini-value">{{ formatNumber(devices.overview.total) }}</div>
+            <div class="mini-hint">宸插惎鐢ㄩ厤缃澶�</div>
+          </div>
+          <div class="mini-card">
+            <div class="mini-label">鍦ㄧ嚎璁惧</div>
+            <div class="mini-value">{{ formatNumber(devices.overview.online) }}</div>
+            <div class="mini-hint">瀹炴椂杩為�氳澶囨暟閲�</div>
+          </div>
+          <div class="mini-card">
+            <div class="mini-label">蹇欑璁惧</div>
+            <div class="mini-value">{{ formatNumber(devices.overview.busy) }}</div>
+            <div class="mini-hint">褰撳墠鎵胯浇浠诲姟鐨勮澶�</div>
+          </div>
+          <div class="mini-card">
+            <div class="mini-label">鍛婅璁惧</div>
+            <div class="mini-value">{{ formatNumber(devices.overview.alarm) }}</div>
+            <div class="mini-hint">鍚樆濉炴垨鎶ヨ鐘舵��</div>
+          </div>
+        </div>
+
+        <div class="chart-card">
+          <div class="chart-title">璁惧鍦ㄧ嚎鍒嗗竷</div>
+          <div ref="deviceTypeChart" class="device-chart-box"></div>
+        </div>
+
+        <div class="type-list">
+          <div v-for="item in devices.typeStats" :key="item.name" class="type-row">
+            <div class="type-row-main">
+              <div class="type-row-name">{{ item.name }}</div>
+              <div class="type-row-desc">鍦ㄧ嚎 {{ formatNumber(item.online) }} / 鎬绘暟 {{ formatNumber(item.total) }}锛岀绾� {{ formatNumber(item.offline) }}</div>
+            </div>
+            <div class="type-row-side">
+              <el-tag size="mini" type="success">蹇欑 {{ formatNumber(item.busy) }}</el-tag>
+              <el-tag size="mini" :type="item.alarm > 0 ? 'danger' : 'info'">鍛婅 {{ formatNumber(item.alarm) }}</el-tag>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <section class="panel panel-network">
+        <div class="panel-header">
+          <div>
+            <div class="panel-kicker">Network</div>
+            <h2 class="panel-title">璁惧缃戠粶鍒嗘瀽</h2>
+            <div class="panel-desc">姹囨�绘渶鏂� Ping 鏍锋湰鐨勮繛閫氭�с�佸欢杩熶笌寮傚父璁惧锛屽府鍔╁揩閫熷彂鐜扮綉缁滄尝鍔ㄣ��</div>
+          </div>
+          <div class="panel-actions">
+            <el-tag size="small" :type="network.overview.attentionDevices > 0 ? 'warning' : 'success'">
+              闇�鍏虫敞 {{ formatNumber(network.overview.attentionDevices) }}
+            </el-tag>
+            <el-button size="mini" plain @click="openDevicePingAnalysis">鏌ョ湅鏄庣粏</el-button>
+          </div>
+        </div>
+
+        <div class="mini-grid">
+          <div class="mini-card network-mini-ok">
+            <div class="mini-label">姝e父</div>
+            <div class="mini-value">{{ formatNumber(network.overview.okDevices) }}</div>
+            <div class="mini-hint">鏈�鏂版牱鏈姸鎬� OK</div>
+          </div>
+          <div class="mini-card network-mini-warning">
+            <div class="mini-label">娉㈠姩</div>
+            <div class="mini-value">{{ formatNumber(network.overview.unstableDevices) }}</div>
+            <div class="mini-hint">閮ㄥ垎鎺㈡祴鎴愬姛</div>
+          </div>
+          <div class="mini-card network-mini-offline">
+            <div class="mini-label">瓒呮椂/寮傚父</div>
+            <div class="mini-value">{{ formatNumber(network.overview.offlineDevices) }}</div>
+            <div class="mini-hint">鏆傛棤鏁版嵁 {{ formatNumber(network.overview.noDataDevices) }}</div>
+          </div>
+          <div class="mini-card network-mini-latency">
+            <div class="mini-label">骞冲潎寤惰繜</div>
+            <div class="mini-value">{{ formatLatency(network.overview.avgLatencyMs) }}</div>
+            <div class="mini-hint">宄板�� {{ formatLatency(network.overview.maxLatencyMs) }}</div>
+          </div>
+        </div>
+
+        <div class="chart-card">
+          <div class="chart-title">杩為�氱姸鎬佸垎甯�</div>
+          <div class="chart-subtitle">{{ networkSamplingText() }}</div>
+          <div ref="networkStatusChart" class="network-chart-box"></div>
+        </div>
+
+        <div class="route-list" v-if="network.focusDevices.length">
+          <div v-for="item in network.focusDevices" :key="item.name + '-' + item.ip" class="route-row">
+            <div class="route-row-main">
+              <div class="route-row-name">{{ item.name }}</div>
+              <div class="route-row-desc">{{ displayText(item.ip, '-') }}</div>
+              <div v-if="item.message" :class="['network-note', 'network-note-' + (item.statusType || 'info')]">{{ item.message }}</div>
+            </div>
+            <div class="route-row-side">
+              <el-tag size="mini" :type="item.statusType">{{ item.statusText }}</el-tag>
+              <div class="route-extra">骞冲潎 {{ formatLatency(item.avgLatencyMs) }}</div>
+              <div class="route-extra">鏈�杩戞牱鏈� {{ displayText(item.latestTimeLabel, '-') }}</div>
+            </div>
+          </div>
+        </div>
+        <div v-else-if="network.overview.totalDevices > 0" class="network-healthy-state">
+          <div class="network-healthy-main">
+            <div class="network-healthy-title">褰撳墠缃戠粶鎺㈡祴绋冲畾</div>
+            <div class="network-healthy-desc">宸茬撼鍏� {{ formatNumber(network.overview.totalDevices) }} 鍙拌澶囷紝鏈�杩戜竴杞湭鍙戠幇瓒呮椂鎴栨尝鍔ㄣ��</div>
+          </div>
+          <div class="network-healthy-tags">
+            <div class="network-healthy-tag">姝e父 {{ formatNumber(network.overview.okDevices) }}</div>
+            <div class="network-healthy-tag">骞冲潎 {{ formatLatency(network.overview.avgLatencyMs) }}</div>
+            <div class="network-healthy-tag">宄板�� {{ formatLatency(network.overview.maxLatencyMs) }}</div>
+          </div>
+        </div>
+        <el-empty v-else description="鏆傛棤璁惧缃戠粶鏍锋湰"></el-empty>
+      </section>
+    </div>
   </div>
 
   <div v-if="loading" class="loading-mask">
@@ -858,6 +1054,6 @@
 <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
 <script type="text/javascript" src="../../static/vue/element/element.js"></script>
 <script type="text/javascript" src="../../static/js/echarts/echarts.min.js"></script>
-<script type="text/javascript" src="../../static/js/dashboard/dashboard.js"></script>
+<script type="text/javascript" src="../../static/js/dashboard/dashboard.js?v=20260317-dashboard-network-focus"></script>
 </body>
 </html>

--
Gitblit v1.9.1