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

---
 src/main/webapp/views/dashboard/dashboard.html |  390 +++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 293 insertions(+), 97 deletions(-)

diff --git a/src/main/webapp/views/dashboard/dashboard.html b/src/main/webapp/views/dashboard/dashboard.html
index 857578d..1a806a2 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;
       }
@@ -580,6 +690,20 @@
         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>
 </head>
@@ -589,63 +713,63 @@
     <div class="hero-main">
       <div class="hero-copy">
         <div class="hero-eyebrow">WCS Dashboard</div>
-        <h1 class="hero-title">绯荤粺浠〃鐩�</h1>
+        <h1 class="hero-title">{{ i18n('dashboard.title', '绯荤粺浠〃鐩�') }}</h1>
       </div>
       <div class="hero-actions">
-        <el-button size="small" plain @click="openMonitor">鎵撳紑鐩戞帶鐢婚潰</el-button>
-        <el-button size="small" type="primary" :loading="refreshing" @click="loadDashboard(true)">绔嬪嵆鍒锋柊</el-button>
+        <el-button size="small" plain @click="openMonitor">{{ i18n('dashboard.openMonitor', '鎵撳紑鐩戞帶鐢婚潰') }}</el-button>
+        <el-button size="small" type="primary" :loading="refreshing" @click="loadDashboard(true)">{{ i18n('dashboard.refreshNow', '绔嬪嵆鍒锋柊') }}</el-button>
       </div>
     </div>
 
     <div class="hero-stat-grid">
       <div class="hero-stat-row">
         <div class="hero-row-head">
-          <div class="hero-row-kicker">鐘舵�佹瑙�</div>
-          <div class="hero-row-note">绯荤粺涓庡埛鏂拌妭濂�</div>
+          <div class="hero-row-kicker">{{ i18n('dashboard.overviewKicker', '鐘舵�佹瑙�') }}</div>
+          <div class="hero-row-note">{{ i18n('dashboard.overviewNote', '绯荤粺涓庡埛鏂拌妭濂�') }}</div>
         </div>
         <div class="hero-status-grid">
           <div class="hero-meta">
-            <div class="hero-meta-label">绯荤粺鐘舵��</div>
-            <div class="hero-meta-value">{{ overview.systemRunning ? '杩愯涓�' : '宸叉殏鍋�' }}</div>
-            <div class="hero-meta-desc">WCS 涓绘湇鍔″綋鍓嶇姸鎬�</div>
+            <div class="hero-meta-label">{{ i18n('dashboard.systemStatusLabel', '绯荤粺鐘舵��') }}</div>
+            <div class="hero-meta-value">{{ overview.systemRunning ? i18n('dashboard.systemRunning', '杩愯涓�') : i18n('dashboard.systemPaused', '宸叉殏鍋�') }}</div>
+            <div class="hero-meta-desc">{{ i18n('dashboard.systemStatusDesc', 'WCS 涓绘湇鍔″綋鍓嶇姸鎬�') }}</div>
           </div>
           <div class="hero-meta">
-            <div class="hero-meta-label">鏈�杩戝埛鏂�</div>
+            <div class="hero-meta-label">{{ i18n('dashboard.lastRefreshLabel', '鏈�杩戝埛鏂�') }}</div>
             <div class="hero-meta-value">{{ displayText(overview.generatedAt, '-') }}</div>
-            <div class="hero-meta-desc">鏈�杩戜竴娆¤仛鍚堟暟鎹敓鎴愭椂闂�</div>
+            <div class="hero-meta-desc">{{ i18n('dashboard.lastRefreshDesc', '鏈�杩戜竴娆¤仛鍚堟暟鎹敓鎴愭椂闂�') }}</div>
           </div>
           <div class="hero-meta">
-            <div class="hero-meta-label">鑷姩鍒锋柊</div>
-            <div class="hero-meta-value">{{ countdown }}s 鍚庡埛鏂�</div>
-            <div class="hero-meta-desc">椤甸潰鑷姩鏇存柊鍊掕鏃�</div>
+            <div class="hero-meta-label">{{ i18n('dashboard.autoRefreshLabel', '鑷姩鍒锋柊') }}</div>
+            <div class="hero-meta-value">{{ i18n('dashboard.autoRefreshValue', '{0}s 鍚庡埛鏂�', [countdown]) }}</div>
+            <div class="hero-meta-desc">{{ i18n('dashboard.autoRefreshDesc', '椤甸潰鑷姩鏇存柊鍊掕鏃�') }}</div>
           </div>
         </div>
       </div>
       <div class="hero-stat-row">
         <div class="hero-row-head">
-          <div class="hero-row-kicker">鏍稿績鎸囨爣</div>
-          <div class="hero-row-note">浠诲姟銆佽澶囦笌 AI 鎬昏</div>
+          <div class="hero-row-kicker">{{ i18n('dashboard.coreMetricsKicker', '鏍稿績鎸囨爣') }}</div>
+          <div class="hero-row-note">{{ i18n('dashboard.coreMetricsNote', '浠诲姟銆佽澶囦笌 AI 鎬昏') }}</div>
         </div>
         <div class="hero-metric-grid">
           <div class="summary-card">
-            <div class="label">浠诲姟鎬绘暟</div>
+            <div class="label">{{ i18n('dashboard.taskTotalLabel', '浠诲姟鎬绘暟') }}</div>
             <div class="value">{{ formatNumber(overview.taskTotal) }}</div>
-            <div class="desc">褰撳墠鎵ц涓� {{ formatNumber(overview.taskRunning) }}</div>
+            <div class="desc">{{ i18n('dashboard.taskTotalDesc', '褰撳墠鎵ц涓� {0}', [formatNumber(overview.taskRunning)]) }}</div>
           </div>
           <div class="summary-card">
-            <div class="label">鍦ㄧ嚎璁惧</div>
+            <div class="label">{{ i18n('dashboard.deviceOnlineLabel', '鍦ㄧ嚎璁惧') }}</div>
             <div class="value">{{ formatNumber(overview.deviceOnline) }}</div>
-            <div class="desc">鎬昏澶� {{ formatNumber(overview.deviceTotal) }}锛屽憡璀� {{ formatNumber(overview.deviceAlarm) }}</div>
+            <div class="desc">{{ i18n('dashboard.deviceOnlineDesc', '鎬昏澶� {0}锛屽憡璀� {1}', [formatNumber(overview.deviceTotal), formatNumber(overview.deviceAlarm)]) }}</div>
           </div>
           <div class="summary-card">
-            <div class="label">AI 绱 Tokens</div>
+            <div class="label">{{ i18n('dashboard.aiTokenTotalLabel', 'AI 绱 Tokens') }}</div>
             <div class="value">{{ formatNumber(overview.aiTokenTotal) }}</div>
-            <div class="desc">鎸� AI 浼氳瘽绱缁熻</div>
+            <div class="desc">{{ i18n('dashboard.aiTokenTotalDesc', '鎸� AI 浼氳瘽绱缁熻') }}</div>
           </div>
           <div class="summary-card">
-            <div class="label">LLM 璋冪敤娆℃暟</div>
+            <div class="label">{{ i18n('dashboard.aiCallTotalLabel', 'LLM 璋冪敤娆℃暟') }}</div>
             <div class="value">{{ formatNumber(overview.aiCallTotal) }}</div>
-            <div class="desc">鏈�杩戜竴杞繍琛屾儏鍐靛凡绾冲叆涓嬫柟 AI 鍖哄煙</div>
+            <div class="desc">{{ i18n('dashboard.aiCallTotalDesc', '鏈�杩戜竴杞繍琛屾儏鍐靛凡绾冲叆涓嬫柟 AI 鍖哄煙') }}</div>
           </div>
         </div>
       </div>
@@ -658,41 +782,41 @@
         <div class="panel-header">
           <div>
             <div class="panel-kicker">Task</div>
-            <h2 class="panel-title">浠诲姟鎬佸娍</h2>
-            <div class="panel-desc">浠庝换鍔$被鍨嬨�佹墽琛岄樁娈靛拰鏈�杩戞祦杞褰曞揩閫熷垽鏂綋鍓嶄綔涓氬帇鍔涖��</div>
+            <h2 class="panel-title">{{ i18n('dashboard.taskPanelTitle', '浠诲姟鎬佸娍') }}</h2>
+            <div class="panel-desc">{{ i18n('dashboard.taskPanelDesc', '浠庝换鍔$被鍨嬨�佹墽琛岄樁娈靛拰鏈�杩戞祦杞褰曞揩閫熷垽鏂綋鍓嶄綔涓氬帇鍔涖��') }}</div>
           </div>
         </div>
 
         <div class="mini-grid">
           <div class="mini-card task-mini-running">
-            <div class="mini-label">鎵ц涓�</div>
+            <div class="mini-label">{{ i18n('dashboard.taskRunningLabel', '鎵ц涓�') }}</div>
             <div class="mini-value">{{ formatNumber(tasks.overview.running) }}</div>
-            <div class="mini-hint">褰撳墠姝e湪娴佽浆鐨勪换鍔�</div>
+            <div class="mini-hint">{{ i18n('dashboard.taskRunningHint', '褰撳墠姝e湪娴佽浆鐨勪换鍔�') }}</div>
           </div>
           <div class="mini-card task-mini-manual">
-            <div class="mini-label">寰呬汉宸�</div>
+            <div class="mini-label">{{ i18n('dashboard.taskManualLabel', '寰呬汉宸�') }}</div>
             <div class="mini-value">{{ formatNumber(tasks.overview.manual) }}</div>
-            <div class="mini-hint">闇�浜哄伐鍏虫敞鎴栧洖婊�</div>
+            <div class="mini-hint">{{ i18n('dashboard.taskManualHint', '闇�浜哄伐鍏虫敞鎴栧洖婊�') }}</div>
           </div>
           <div class="mini-card task-mini-completed">
-            <div class="mini-label">宸插畬鎴�</div>
+            <div class="mini-label">{{ i18n('dashboard.taskCompletedLabel', '宸插畬鎴�') }}</div>
             <div class="mini-value">{{ formatNumber(tasks.overview.completed) }}</div>
-            <div class="mini-hint">宸茬粡瀹屾垚鎴栬惤璐�</div>
+            <div class="mini-hint">{{ i18n('dashboard.taskCompletedHint', '宸茬粡瀹屾垚鎴栬惤璐�') }}</div>
           </div>
           <div class="mini-card task-mini-new">
-            <div class="mini-label">鏂板缓</div>
+            <div class="mini-label">{{ i18n('dashboard.taskNewLabel', '鏂板缓') }}</div>
             <div class="mini-value">{{ formatNumber(tasks.overview.newCreated) }}</div>
-            <div class="mini-hint">鍒氳繘鍏ヨ皟搴︽祦绋�</div>
+            <div class="mini-hint">{{ i18n('dashboard.taskNewHint', '鍒氳繘鍏ヨ皟搴︽祦绋�') }}</div>
           </div>
         </div>
 
         <div class="chart-grid">
           <div class="chart-card">
-            <div class="chart-title">浠诲姟绫诲瀷鍒嗗竷</div>
+            <div class="chart-title">{{ i18n('dashboard.taskDirectionChartTitle', '浠诲姟绫诲瀷鍒嗗竷') }}</div>
             <div ref="taskDirectionChart" class="chart-box"></div>
           </div>
           <div class="chart-card">
-            <div class="chart-title">浠诲姟闃舵姒傝</div>
+            <div class="chart-title">{{ i18n('dashboard.taskStageChartTitle', '浠诲姟闃舵姒傝') }}</div>
             <div ref="taskStageChart" class="chart-box"></div>
           </div>
         </div>
@@ -709,8 +833,8 @@
         <div class="panel-header">
           <div>
             <div class="panel-kicker">Recent</div>
-            <h2 class="panel-title">鏈�杩戜换鍔�</h2>
-            <div class="panel-desc">甯姪蹇�熷垽鏂换鍔℃槸鍚﹀爢绉�佹槸鍚﹁璁惧鎺ユ墜锛屼互鍙婃渶杩戠殑浠诲姟鐩爣浣嶇疆銆�</div>
+            <h2 class="panel-title">{{ i18n('dashboard.recentPanelTitle', '鏈�杩戜换鍔�') }}</h2>
+            <div class="panel-desc">{{ i18n('dashboard.recentPanelDesc', '甯姪蹇�熷垽鏂换鍔℃槸鍚﹀爢绉�佹槸鍚﹁璁惧鎺ユ墜锛屼互鍙婃渶杩戠殑浠诲姟鐩爣浣嶇疆銆�') }}</div>
           </div>
         </div>
 
@@ -720,17 +844,72 @@
               stripe
               size="mini"
               height="360"
-              empty-text="鏆傛棤浠诲姟璁板綍">
-            <el-table-column prop="wrkNo" label="浠诲姟鍙�" min-width="100"></el-table-column>
-            <el-table-column prop="taskType" label="浠诲姟绫诲瀷" min-width="110"></el-table-column>
-            <el-table-column prop="status" label="鐘舵��" min-width="160" show-overflow-tooltip></el-table-column>
-            <el-table-column prop="source" label="鏉ユ簮" min-width="170" show-overflow-tooltip></el-table-column>
-            <el-table-column prop="target" label="鐩爣" min-width="170" show-overflow-tooltip></el-table-column>
-            <el-table-column prop="device" label="鎵ц璁惧" min-width="180" show-overflow-tooltip></el-table-column>
-            <el-table-column prop="barcode" label="鏉$爜" min-width="150" show-overflow-tooltip></el-table-column>
-            <el-table-column prop="updateTime" label="鏈�杩戞洿鏂版椂闂�" min-width="170"></el-table-column>
+              :empty-text="i18n('dashboard.recentEmpty', '鏆傛棤浠诲姟璁板綍')">
+            <el-table-column prop="wrkNo" :label="i18n('dashboard.column.workNo', '浠诲姟鍙�')" min-width="100"></el-table-column>
+            <el-table-column prop="taskType" :label="i18n('dashboard.column.taskType', '浠诲姟绫诲瀷')" min-width="110"></el-table-column>
+            <el-table-column prop="status" :label="i18n('dashboard.column.status', '鐘舵��')" min-width="160" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="source" :label="i18n('dashboard.column.source', '鏉ユ簮')" min-width="170" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="target" :label="i18n('dashboard.column.target', '鐩爣')" min-width="170" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="device" :label="i18n('dashboard.column.device', '鎵ц璁惧')" min-width="180" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="barcode" :label="i18n('dashboard.column.barcode', '鏉$爜')" min-width="150" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="updateTime" :label="i18n('dashboard.column.updateTime', '鏈�杩戞洿鏂版椂闂�')" min-width="170"></el-table-column>
           </el-table>
         </div>
+      </section>
+
+      <section class="panel panel-ai">
+        <div class="panel-header">
+          <div>
+            <div class="panel-kicker">AI</div>
+            <h2 class="panel-title">{{ i18n('dashboard.aiPanelTitle', 'AI 杩愯鎯呭喌') }}</h2>
+            <div class="panel-desc">{{ i18n('dashboard.aiPanelDesc', '鏌ョ湅 AI 浼氳瘽绱 Tokens銆丩LM 璋冪敤閲忥紝浠ュ強璺敱鐨勫彲鐢ㄤ笌鍐峰嵈鐘舵�併��') }}</div>
+          </div>
+          <el-tag size="small" type="success">{{ i18n('dashboard.availableRoutesTag', '鍙敤璺敱 {0}', [formatNumber(ai.overview.availableRouteCount)]) }}</el-tag>
+        </div>
+
+        <div class="mini-grid">
+          <div class="mini-card">
+            <div class="mini-label">{{ i18n('dashboard.aiTokenCardLabel', '绱 Tokens') }}</div>
+            <div class="mini-value">{{ formatNumber(ai.overview.tokenTotal) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.aiTokenCardHint', 'Prompt + Completion') }}</div>
+          </div>
+          <div class="mini-card">
+            <div class="mini-label">{{ i18n('dashboard.aiAskCountLabel', '鎻愰棶杞') }}</div>
+            <div class="mini-value">{{ formatNumber(ai.overview.askCount) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.aiAskCountHint', 'AI 瀵硅瘽绱杞') }}</div>
+          </div>
+          <div class="mini-card">
+            <div class="mini-label">{{ i18n('dashboard.aiLlmCallLabel', 'LLM 璋冪敤') }}</div>
+            <div class="mini-value">{{ formatNumber(ai.overview.llmCallTotal) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.aiLlmCallHint', '鎴愬姛 {0} / 澶辫触 {1}', [formatNumber(ai.overview.successCallTotal), formatNumber(ai.overview.failCallTotal)]) }}</div>
+          </div>
+          <div class="mini-card">
+            <div class="mini-label">{{ i18n('dashboard.aiSessionCountLabel', '浼氳瘽鏁�') }}</div>
+            <div class="mini-value">{{ formatNumber(ai.overview.sessionCount) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.aiSessionCountHint', '鏈�杩戣皟鐢� {0}', [displayText(ai.overview.lastCallTime, '-')]) }}</div>
+          </div>
+        </div>
+
+        <div class="chart-card">
+          <div class="chart-title">{{ i18n('dashboard.aiRouteChartTitle', 'AI 璺敱鐘舵��') }}</div>
+          <div ref="aiRouteChart" class="ai-chart-box"></div>
+        </div>
+
+        <div class="route-list" v-if="ai.routeList.length">
+          <div v-for="route in ai.routeList.slice(0, 6)" :key="route.name + '-' + route.model + '-' + route.priority" class="route-row">
+            <div class="route-row-main">
+              <div class="route-row-name">{{ route.name }}</div>
+              <div class="route-row-desc">{{ i18n('dashboard.aiRouteDesc', '妯″瀷 {0}锛屼紭鍏堢骇 {1}', [displayText(route.model, '-'), displayText(route.priority, '-')]) }}</div>
+              <div v-if="route.lastError" class="route-error">{{ route.lastError }}</div>
+            </div>
+            <div class="route-row-side">
+              <el-tag size="mini" :type="route.statusType">{{ route.statusText }}</el-tag>
+              <div class="route-extra">{{ i18n('dashboard.aiRouteResult', '鎴愬姛 {0} / 澶辫触 {1}', [formatNumber(route.successCount), formatNumber(route.failCount)]) }}</div>
+              <div class="route-extra">{{ i18n('dashboard.aiRouteLastUsed', '鏈�杩戜娇鐢� {0}', [displayText(route.lastUsedTime, '-')]) }}</div>
+            </div>
+          </div>
+        </div>
+        <el-empty v-else :description="i18n('dashboard.aiRouteEmpty', '鏆傛棤 AI 璺敱鏁版嵁')"></el-empty>
       </section>
     </div>
 
@@ -739,37 +918,37 @@
         <div class="panel-header">
           <div>
             <div class="panel-kicker">Devices</div>
-            <h2 class="panel-title">璁惧鎬佸娍</h2>
-            <div class="panel-desc">姹囨�昏緭閫佺珯鐐广�佸爢鍨涙満銆佸弻宸ヤ綅鍫嗗灈鏈轰笌 RGV 鐨勫湪绾裤�佸繖纰屽拰鍛婅鎯呭喌銆�</div>
+            <h2 class="panel-title">{{ i18n('dashboard.devicePanelTitle', '璁惧鎬佸娍') }}</h2>
+            <div class="panel-desc">{{ i18n('dashboard.devicePanelDesc', '姹囨�昏緭閫佺珯鐐广�佸爢鍨涙満銆佸弻宸ヤ綅鍫嗗灈鏈轰笌 RGV 鐨勫湪绾裤�佸繖纰屽拰鍛婅鎯呭喌銆�') }}</div>
           </div>
-          <el-tag size="small" type="info">鍦ㄧ嚎鐜� {{ devices.overview.onlineRate || 0 }}%</el-tag>
+          <el-tag size="small" type="info">{{ i18n('dashboard.deviceOnlineRate', '鍦ㄧ嚎鐜� {0}', [formatPercentValue(devices.overview.onlineRate || 0)]) }}</el-tag>
         </div>
 
         <div class="mini-grid">
           <div class="mini-card">
-            <div class="mini-label">璁惧鎬绘暟</div>
+            <div class="mini-label">{{ i18n('dashboard.deviceTotalLabel', '璁惧鎬绘暟') }}</div>
             <div class="mini-value">{{ formatNumber(devices.overview.total) }}</div>
-            <div class="mini-hint">宸插惎鐢ㄩ厤缃澶�</div>
+            <div class="mini-hint">{{ i18n('dashboard.deviceTotalHint', '宸插惎鐢ㄩ厤缃澶�') }}</div>
           </div>
           <div class="mini-card">
-            <div class="mini-label">鍦ㄧ嚎璁惧</div>
+            <div class="mini-label">{{ i18n('dashboard.deviceOnlineCardLabel', '鍦ㄧ嚎璁惧') }}</div>
             <div class="mini-value">{{ formatNumber(devices.overview.online) }}</div>
-            <div class="mini-hint">瀹炴椂杩為�氳澶囨暟閲�</div>
+            <div class="mini-hint">{{ i18n('dashboard.deviceOnlineCardHint', '瀹炴椂杩為�氳澶囨暟閲�') }}</div>
           </div>
           <div class="mini-card">
-            <div class="mini-label">蹇欑璁惧</div>
+            <div class="mini-label">{{ i18n('dashboard.deviceBusyLabel', '蹇欑璁惧') }}</div>
             <div class="mini-value">{{ formatNumber(devices.overview.busy) }}</div>
-            <div class="mini-hint">褰撳墠鎵胯浇浠诲姟鐨勮澶�</div>
+            <div class="mini-hint">{{ i18n('dashboard.deviceBusyHint', '褰撳墠鎵胯浇浠诲姟鐨勮澶�') }}</div>
           </div>
           <div class="mini-card">
-            <div class="mini-label">鍛婅璁惧</div>
+            <div class="mini-label">{{ i18n('dashboard.deviceAlarmLabel', '鍛婅璁惧') }}</div>
             <div class="mini-value">{{ formatNumber(devices.overview.alarm) }}</div>
-            <div class="mini-hint">鍚樆濉炴垨鎶ヨ鐘舵��</div>
+            <div class="mini-hint">{{ i18n('dashboard.deviceAlarmHint', '鍚樆濉炴垨鎶ヨ鐘舵��') }}</div>
           </div>
         </div>
 
         <div class="chart-card">
-          <div class="chart-title">璁惧鍦ㄧ嚎鍒嗗竷</div>
+          <div class="chart-title">{{ i18n('dashboard.deviceTypeChartTitle', '璁惧鍦ㄧ嚎鍒嗗竷') }}</div>
           <div ref="deviceTypeChart" class="device-chart-box"></div>
         </div>
 
@@ -777,69 +956,86 @@
           <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 class="type-row-desc">{{ i18n('dashboard.deviceTypeDesc', '鍦ㄧ嚎 {0} / 鎬绘暟 {1}锛岀绾� {2}', [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>
+              <el-tag size="mini" type="success">{{ i18n('dashboard.deviceBusyTag', '蹇欑 {0}', [formatNumber(item.busy)]) }}</el-tag>
+              <el-tag size="mini" :type="item.alarm > 0 ? 'danger' : 'info'">{{ i18n('dashboard.deviceAlarmTag', '鍛婅 {0}', [formatNumber(item.alarm)]) }}</el-tag>
             </div>
           </div>
         </div>
       </section>
 
-      <section class="panel panel-ai">
+      <section class="panel panel-network">
         <div class="panel-header">
           <div>
-            <div class="panel-kicker">AI</div>
-            <h2 class="panel-title">AI 杩愯鎯呭喌</h2>
-            <div class="panel-desc">鏌ョ湅 AI 浼氳瘽绱 Tokens銆丩LM 璋冪敤閲忥紝浠ュ強璺敱鐨勫彲鐢ㄤ笌鍐峰嵈鐘舵�併��</div>
+            <div class="panel-kicker">Network</div>
+            <h2 class="panel-title">{{ i18n('devicePingLog.title', '璁惧缃戠粶鍒嗘瀽') }}</h2>
+            <div class="panel-desc">{{ i18n('dashboard.networkPanelDesc', '姹囨�绘渶鏂� Ping 鏍锋湰鐨勮繛閫氭�с�佸欢杩熶笌寮傚父璁惧锛屽府鍔╁揩閫熷彂鐜扮綉缁滄尝鍔ㄣ��') }}</div>
           </div>
-          <el-tag size="small" type="success">鍙敤璺敱 {{ formatNumber(ai.overview.availableRouteCount) }}</el-tag>
+          <div class="panel-actions">
+            <el-tag size="small" :type="network.overview.attentionDevices > 0 ? 'warning' : 'success'">
+              {{ i18n('dashboard.networkAttentionTag', '闇�鍏虫敞 {0}', [formatNumber(network.overview.attentionDevices)]) }}
+            </el-tag>
+            <el-button size="mini" plain @click="openDevicePingAnalysis">{{ i18n('dashboard.networkViewDetail', '鏌ョ湅鏄庣粏') }}</el-button>
+          </div>
         </div>
 
         <div class="mini-grid">
-          <div class="mini-card">
-            <div class="mini-label">绱 Tokens</div>
-            <div class="mini-value">{{ formatNumber(ai.overview.tokenTotal) }}</div>
-            <div class="mini-hint">Prompt + Completion</div>
+          <div class="mini-card network-mini-ok">
+            <div class="mini-label">{{ i18n('dashboard.networkOkLabel', '姝e父') }}</div>
+            <div class="mini-value">{{ formatNumber(network.overview.okDevices) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.networkOkHint', '鏈�鏂版牱鏈姸鎬� OK') }}</div>
           </div>
-          <div class="mini-card">
-            <div class="mini-label">鎻愰棶杞</div>
-            <div class="mini-value">{{ formatNumber(ai.overview.askCount) }}</div>
-            <div class="mini-hint">AI 瀵硅瘽绱杞</div>
+          <div class="mini-card network-mini-warning">
+            <div class="mini-label">{{ i18n('dashboard.networkUnstableLabel', '娉㈠姩') }}</div>
+            <div class="mini-value">{{ formatNumber(network.overview.unstableDevices) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.networkUnstableHint', '閮ㄥ垎鎺㈡祴鎴愬姛') }}</div>
           </div>
-          <div class="mini-card">
-            <div class="mini-label">LLM 璋冪敤</div>
-            <div class="mini-value">{{ formatNumber(ai.overview.llmCallTotal) }}</div>
-            <div class="mini-hint">鎴愬姛 {{ formatNumber(ai.overview.successCallTotal) }} / 澶辫触 {{ formatNumber(ai.overview.failCallTotal) }}</div>
+          <div class="mini-card network-mini-offline">
+            <div class="mini-label">{{ i18n('dashboard.networkOfflineLabel', '瓒呮椂/寮傚父') }}</div>
+            <div class="mini-value">{{ formatNumber(network.overview.offlineDevices) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.networkNoDataHint', '鏆傛棤鏁版嵁 {0}', [formatNumber(network.overview.noDataDevices)]) }}</div>
           </div>
-          <div class="mini-card">
-            <div class="mini-label">浼氳瘽鏁�</div>
-            <div class="mini-value">{{ formatNumber(ai.overview.sessionCount) }}</div>
-            <div class="mini-hint">鏈�杩戣皟鐢� {{ displayText(ai.overview.lastCallTime, '-') }}</div>
+          <div class="mini-card network-mini-latency">
+            <div class="mini-label">{{ i18n('dashboard.networkAvgLatencyLabel', '骞冲潎寤惰繜') }}</div>
+            <div class="mini-value">{{ formatLatency(network.overview.avgLatencyMs) }}</div>
+            <div class="mini-hint">{{ i18n('dashboard.networkPeakLatencyHint', '宄板�� {0}', [formatLatency(network.overview.maxLatencyMs)]) }}</div>
           </div>
         </div>
 
         <div class="chart-card">
-          <div class="chart-title">AI 璺敱鐘舵��</div>
-          <div ref="aiRouteChart" class="ai-chart-box"></div>
+          <div class="chart-title">{{ i18n('dashboard.networkStatusChartTitle', '杩為�氱姸鎬佸垎甯�') }}</div>
+          <div class="chart-subtitle">{{ networkSamplingText() }}</div>
+          <div ref="networkStatusChart" class="network-chart-box"></div>
         </div>
 
-        <div class="route-list" v-if="ai.routeList.length">
-          <div v-for="route in ai.routeList.slice(0, 6)" :key="route.name + '-' + route.model + '-' + route.priority" class="route-row">
+        <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">{{ route.name }}</div>
-              <div class="route-row-desc">妯″瀷 {{ displayText(route.model, '-') }}锛屼紭鍏堢骇 {{ displayText(route.priority, '-') }}</div>
-              <div v-if="route.lastError" class="route-error">{{ route.lastError }}</div>
+              <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="route.statusType">{{ route.statusText }}</el-tag>
-              <div class="route-extra">鎴愬姛 {{ formatNumber(route.successCount) }} / 澶辫触 {{ formatNumber(route.failCount) }}</div>
-              <div class="route-extra">鏈�杩戜娇鐢� {{ displayText(route.lastUsedTime, '-') }}</div>
+              <el-tag size="mini" :type="item.statusType">{{ item.statusText }}</el-tag>
+              <div class="route-extra">{{ i18n('dashboard.networkAvgLatencyTag', '骞冲潎 {0}', [formatLatency(item.avgLatencyMs)]) }}</div>
+              <div class="route-extra">{{ i18n('dashboard.networkLatestSampleTag', '鏈�杩戞牱鏈� {0}', [displayText(item.latestTimeLabel, '-')]) }}</div>
             </div>
           </div>
         </div>
-        <el-empty v-else description="鏆傛棤 AI 璺敱鏁版嵁"></el-empty>
+        <div v-else-if="network.overview.totalDevices > 0" class="network-healthy-state">
+          <div class="network-healthy-main">
+            <div class="network-healthy-title">{{ i18n('dashboard.networkHealthyTitle', '褰撳墠缃戠粶鎺㈡祴绋冲畾') }}</div>
+            <div class="network-healthy-desc">{{ i18n('dashboard.networkHealthyDesc', '宸茬撼鍏� {0} 鍙拌澶囷紝鏈�杩戜竴杞湭鍙戠幇瓒呮椂鎴栨尝鍔ㄣ��', [formatNumber(network.overview.totalDevices)]) }}</div>
+          </div>
+          <div class="network-healthy-tags">
+            <div class="network-healthy-tag">{{ i18n('dashboard.networkHealthyOk', '姝e父 {0}', [formatNumber(network.overview.okDevices)]) }}</div>
+            <div class="network-healthy-tag">{{ i18n('dashboard.networkHealthyAvg', '骞冲潎 {0}', [formatLatency(network.overview.avgLatencyMs)]) }}</div>
+            <div class="network-healthy-tag">{{ i18n('dashboard.networkHealthyPeak', '宄板�� {0}', [formatLatency(network.overview.maxLatencyMs)]) }}</div>
+          </div>
+        </div>
+        <el-empty v-else :description="i18n('dashboard.networkEmpty', '鏆傛棤璁惧缃戠粶鏍锋湰')"></el-empty>
       </section>
     </div>
   </div>
@@ -847,8 +1043,8 @@
   <div v-if="loading" class="loading-mask">
     <div class="loading-card">
       <i class="el-icon-loading" style="font-size: 26px;"></i>
-      <div class="loading-title">姝e湪鍔犺浇浠〃鐩�</div>
-      <div class="loading-desc">姹囨�讳换鍔°�佽澶囦笌 AI 杩愯鏁版嵁锛岃绋嶅��...</div>
+      <div class="loading-title">{{ i18n('dashboard.loadingTitle', '姝e湪鍔犺浇浠〃鐩�') }}</div>
+      <div class="loading-desc">{{ i18n('dashboard.loadingDesc', '姹囨�讳换鍔°�佽澶囦笌 AI 杩愯鏁版嵁锛岃绋嶅��...') }}</div>
     </div>
   </div>
 </div>
@@ -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-i18n"></script>
 </body>
 </html>

--
Gitblit v1.9.1