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/resources/i18n/zh-CN/messages.properties | 188 ++++++++
src/main/webapp/static/js/devicePingLog/devicePingLog.js | 200 ++++++++-
src/main/webapp/static/js/dashboard/dashboard.js | 264 +++++++++++
src/main/resources/i18n/en-US/messages.properties | 188 ++++++++
src/main/resources/i18n/zh-CN/legacy.properties | 49 ++
src/main/resources/i18n/en-US/legacy.properties | 48 ++
src/main/webapp/views/dashboard/dashboard.html | 202 ++++----
src/main/webapp/views/devicePingLog/devicePingLog.html | 120 ++--
8 files changed, 1,046 insertions(+), 213 deletions(-)
diff --git a/src/main/resources/i18n/en-US/legacy.properties b/src/main/resources/i18n/en-US/legacy.properties
index e43aa6d..5c33986 100644
--- a/src/main/resources/i18n/en-US/legacy.properties
+++ b/src/main/resources/i18n/en-US/legacy.properties
@@ -1081,6 +1081,54 @@
鏂板绯荤粺閰嶇疆=Add System Configuration
淇敼绯荤粺閰嶇疆=Edit System Configuration
璇︽儏绯荤粺閰嶇疆=System Configuration Details
+
+# Dashboard and device ping pages
+绯荤粺浠〃鐩�=System Dashboard
+璁惧缃戠粶鍒嗘瀽=Device Network Analysis
+鐩戞帶鐢婚潰=Monitoring View
+鐩戞帶绯荤粺=Monitoring
+鍏ュ簱浠诲姟=Inbound Tasks
+鍑哄簱浠诲姟=Outbound Tasks
+鎵ц涓�=Running
+寰呬汉宸�=Manual Review
+宸插畬鎴�=Completed
+鏂板缓=New
+鐢熸垚鍏ュ簱浠诲姟=Create Inbound Task
+璁惧涓婅蛋=Device Moving Up
+璁惧鎼繍涓�=Device Handling
+璁惧鎼繍瀹屾垚=Device Handling Completed
+鍏ュ簱寰呬汉宸ュ洖婊�=Inbound Pending Manual Rollback
+鍏ュ簱瀹屾垚=Inbound Completed
+鍏ュ簱搴撳瓨鏇存柊=Inbound Inventory Updated
+鐢熸垚鍑哄簱浠诲姟=Create Outbound Task
+绔欑偣杩愯涓�=Station Running
+绔欑偣杩愯瀹屾垚=Station Run Completed
+鍑哄簱寰呬汉宸ュ洖婊�=Outbound Pending Manual Rollback
+鍑哄簱瀹屾垚=Outbound Completed
+鍑哄簱搴撳瓨鏇存柊=Outbound Inventory Updated
+鐢熸垚绉诲簱浠诲姟=Create Transfer Task
+绉诲簱寰呬汉宸ュ洖婊�=Transfer Pending Manual Rollback
+绉诲簱瀹屾垚=Transfer Completed
+杈撻�佺珯鐐�=Conveyor Stations
+鍙敤=Available
+宸茬鐢�=Disabled
+瓒呮椂=Timeout
+娉㈠姩=Unstable
+瓒呮椂/寮傚父=Timeout/Error
+鏈畾涔夌姸鎬�=Undefined Status
+鍏ㄩ儴鎺㈡祴鍧囪秴鏃�=All probes timed out
+鏆傛棤棰濆璇存槑=No additional details
+鎺㈡祴澶辫触=Probe failed
+璇锋眰瓒呮椂=Request timeout
+鎵句笉鍒颁富鏈�=Could not find host
+鏃犳硶璁块棶鐩爣涓绘満=Destination host unreachable
+100.0% 涓㈠け=100.0% packet loss
+regex\:^鐘舵��(\d+)$=Status $1
+regex\:^绔欑偣(\d+)$=Station $1
+regex\:^绔欑偣(\d+)\s*/\s*(.+)$=Station $1 / $2
+regex\:^鍫嗗灈鏈�#(\d+)$=Crane #$1
+regex\:^鍙屽伐浣�#(\d+)$=Dual Station #$1
+regex\:^RGV#(\d+)$=RGV #$1
鏄惁Delete=Deleted
绉� 鎴�=Tenant
璐� 鍙�=Account
diff --git a/src/main/resources/i18n/en-US/messages.properties b/src/main/resources/i18n/en-US/messages.properties
index 834d853..606a9e1 100644
--- a/src/main/resources/i18n/en-US/messages.properties
+++ b/src/main/resources/i18n/en-US/messages.properties
@@ -213,6 +213,194 @@
legacy.regex.stationDeviceLink=Click to remove mapping: Station {0} -> Device {1}
deviceLogs.visualizationPrefix=Device Logs -
deviceLogs.downloadDialogTitle=Downloading file
+dashboard.title=System Dashboard
+dashboard.monitorView=Monitoring View
+dashboard.openMonitor=Open Monitoring View
+dashboard.refreshNow=Refresh Now
+dashboard.overviewKicker=Overview
+dashboard.overviewNote=System and refresh cadence
+dashboard.systemStatusLabel=System Status
+dashboard.systemRunning=Running
+dashboard.systemPaused=Paused
+dashboard.systemStatusDesc=Current status of the WCS main service
+dashboard.lastRefreshLabel=Last Refresh
+dashboard.lastRefreshDesc=Time when the latest aggregated data was generated
+dashboard.autoRefreshLabel=Auto Refresh
+dashboard.autoRefreshValue=Refresh in {0}s
+dashboard.autoRefreshDesc=Countdown to the next automatic refresh
+dashboard.coreMetricsKicker=Core Metrics
+dashboard.coreMetricsNote=Tasks, devices, and AI overview
+dashboard.taskTotalLabel=Total Tasks
+dashboard.taskTotalDesc=Running now {0}
+dashboard.deviceOnlineLabel=Online Devices
+dashboard.deviceOnlineDesc=Total {0}, alarms {1}
+dashboard.aiTokenTotalLabel=AI Total Tokens
+dashboard.aiTokenTotalDesc=Aggregated across AI sessions
+dashboard.aiCallTotalLabel=LLM Calls
+dashboard.aiCallTotalDesc=Latest run details are included in the AI section below
+dashboard.taskPanelTitle=Task Overview
+dashboard.taskPanelDesc=Quickly assess current workload pressure by task type, execution stage, and recent flow records.
+dashboard.taskRunningLabel=Running
+dashboard.taskRunningHint=Tasks currently in progress
+dashboard.taskManualLabel=Manual Review
+dashboard.taskManualHint=Requires manual attention or rollback
+dashboard.taskCompletedLabel=Completed
+dashboard.taskCompletedHint=Finished or posted
+dashboard.taskNewLabel=New
+dashboard.taskNewHint=Just entered the dispatch flow
+dashboard.taskDirectionChartTitle=Task Type Distribution
+dashboard.taskStageChartTitle=Task Stage Overview
+dashboard.recentPanelTitle=Recent Tasks
+dashboard.recentPanelDesc=Quickly tell whether tasks are piling up, whether devices have taken over, and where recent tasks are headed.
+dashboard.recentEmpty=No recent tasks
+dashboard.column.workNo=Work No.
+dashboard.column.taskType=Task Type
+dashboard.column.status=Status
+dashboard.column.source=Source
+dashboard.column.target=Target
+dashboard.column.device=Execution Device
+dashboard.column.barcode=Barcode
+dashboard.column.updateTime=Last Updated
+dashboard.aiPanelTitle=AI Activity
+dashboard.aiPanelDesc=Review cumulative AI tokens, LLM call volume, and route availability and cooldown status.
+dashboard.availableRoutesTag=Available routes {0}
+dashboard.aiTokenCardLabel=Total Tokens
+dashboard.aiTokenCardHint=Prompt + Completion
+dashboard.aiAskCountLabel=Conversation Turns
+dashboard.aiAskCountHint=Total AI conversation turns
+dashboard.aiLlmCallLabel=LLM Calls
+dashboard.aiLlmCallHint=Success {0} / Failed {1}
+dashboard.aiSessionCountLabel=Sessions
+dashboard.aiSessionCountHint=Last call {0}
+dashboard.aiRouteChartTitle=AI Route Status
+dashboard.aiRouteDesc=Model {0}, priority {1}
+dashboard.aiRouteResult=Success {0} / Failed {1}
+dashboard.aiRouteLastUsed=Last used {0}
+dashboard.aiRouteEmpty=No AI route data
+dashboard.devicePanelTitle=Device Overview
+dashboard.devicePanelDesc=Summarize the online, busy, and alarm states of conveyor stations, cranes, dual-station cranes, and RGVs.
+dashboard.deviceOnlineRate=Online rate {0}
+dashboard.deviceTotalLabel=Total Devices
+dashboard.deviceTotalHint=Enabled configured devices
+dashboard.deviceOnlineCardLabel=Online Devices
+dashboard.deviceOnlineCardHint=Devices currently reachable
+dashboard.deviceBusyLabel=Busy Devices
+dashboard.deviceBusyHint=Devices currently carrying tasks
+dashboard.deviceAlarmLabel=Alarm Devices
+dashboard.deviceAlarmHint=Devices with blockage or alarm status
+dashboard.deviceTypeChartTitle=Device Online Distribution
+dashboard.deviceTypeDesc=Online {0} / Total {1}, offline {2}
+dashboard.deviceBusyTag=Busy {0}
+dashboard.deviceAlarmTag=Alarm {0}
+dashboard.networkPanelDesc=Summarize connectivity, latency, and abnormal devices from the latest ping samples to quickly spot network fluctuations.
+dashboard.networkAttentionTag=Attention {0}
+dashboard.networkViewDetail=View Details
+dashboard.networkOkLabel=Normal
+dashboard.networkOkHint=Latest sample status is OK
+dashboard.networkUnstableLabel=Unstable
+dashboard.networkUnstableHint=Some probes succeeded
+dashboard.networkOfflineLabel=Timeout/Error
+dashboard.networkNoDataLabel=No Data
+dashboard.networkNoDataHint=No data {0}
+dashboard.networkAvgLatencyLabel=Average Latency
+dashboard.networkPeakLatencyHint=Peak {0}
+dashboard.networkStatusChartTitle=Connectivity Status Distribution
+dashboard.networkAvgLatencyTag=Avg {0}
+dashboard.networkLatestSampleTag=Latest sample {0}
+dashboard.networkHealthyTitle=Network probing is stable
+dashboard.networkHealthyDesc={0} devices are included, and no timeouts or fluctuations were found in the latest round.
+dashboard.networkHealthyOk=Normal {0}
+dashboard.networkHealthyAvg=Avg {0}
+dashboard.networkHealthyPeak=Peak {0}
+dashboard.networkEmpty=No device network samples
+dashboard.loadingTitle=Loading dashboard
+dashboard.loadingDesc=Aggregating task, device, and AI runtime data. Please wait...
+dashboard.loadFailed=Failed to load dashboard data
+dashboard.loadFailedDetail=Failed to load dashboard data. Please check the API status.
+dashboard.deviceOnlineLegend=Online
+dashboard.deviceOfflineLegend=Offline
+dashboard.chartCenter.availableRoutes=Available Routes
+dashboard.chartCenter.attentionDevices=Devices Requiring Attention
+dashboard.systemDefaultPacketSize=System Default
+dashboard.networkSampling=Sampling {0} ms / Timeout {1} ms / {2} probes per sample / Packet size {3}
+dashboard.taskDirectionInbound=Inbound Tasks
+dashboard.taskDirectionOutbound=Outbound Tasks
+dashboard.taskDirectionMove=Transfer Tasks
+dashboard.aiRouteStatusAvailable=Available
+dashboard.aiRouteStatusCooling=Cooling
+dashboard.aiRouteStatusDisabled=Disabled
+devicePingLog.title=Device Network Analysis
+devicePingLog.pageMeta=Packet size {0}, {1}
+devicePingLog.summary.totalDevices=Total Devices
+devicePingLog.summary.totalDevicesHint=Devices with configured IPs
+devicePingLog.summary.ok=Normal
+devicePingLog.summary.okHint=Latest sample status is OK
+devicePingLog.summary.unstable=Unstable
+devicePingLog.summary.unstableHint=Some probes succeeded
+devicePingLog.summary.offline=Timeout/Error
+devicePingLog.summary.offlineHint=Latest sample is unreachable
+devicePingLog.summary.noData=No Data
+devicePingLog.summary.noDataHint=No persisted samples yet
+devicePingLog.summary.avgLatency=Overall Average Latency
+devicePingLog.summary.peakLatency=Peak {0}
+devicePingLog.overviewTitle=Device Overview
+devicePingLog.refresh=Refresh
+devicePingLog.filter.deviceType=Device Type
+devicePingLog.filter.all=All
+devicePingLog.filter.keyword=Keyword
+devicePingLog.filter.keywordPlaceholder=Device No. / IP
+devicePingLog.overviewCount=Overview devices {0}
+devicePingLog.column.device=Device
+devicePingLog.column.status=Status
+devicePingLog.column.successRate=Success Rate
+devicePingLog.column.avg=Avg
+devicePingLog.column.min=Min
+devicePingLog.column.max=Max
+devicePingLog.column.updateTime=Updated At
+devicePingLog.column.message=Message
+devicePingLog.column.action=Action
+devicePingLog.viewDetail=View Details
+devicePingLog.detailTitle=Device Details
+devicePingLog.detailEmpty=Select a device from the overview above to inspect second-level details.
+devicePingLog.filter.timeRange=Time Range
+devicePingLog.filter.rangeSeparator=to
+devicePingLog.filter.start=Start
+devicePingLog.filter.end=End
+devicePingLog.quickRange.30m=30 min
+devicePingLog.quickRange.1h=1 hr
+devicePingLog.quickRange.6h=6 hr
+devicePingLog.query=Query
+devicePingLog.detail.status=Status
+devicePingLog.detail.packetSize=Packet Size
+devicePingLog.detail.successRate=Success Rate
+devicePingLog.detail.avg=Avg
+devicePingLog.detail.min=Min
+devicePingLog.detail.max=Max
+devicePingLog.chart.latencyTitle=Latency
+devicePingLog.chart.empty=No second-level samples in the current range
+devicePingLog.chart.availabilityTitle=Success Rate / Fail Count
+devicePingLog.alertColumn.time=Time
+devicePingLog.alertColumn.status=Status
+devicePingLog.alertColumn.message=Message
+devicePingLog.samplingConfigText=Sampling {0} ms / Timeout {1} ms / {2} probes per sample
+devicePingLog.optionsLoadFailed=Failed to load device configuration
+devicePingLog.overviewLoadFailed=Failed to load overview data
+devicePingLog.selectTimeRange=Please select a time range
+devicePingLog.invalidDevice=Invalid device information
+devicePingLog.detailLoadFailed=Failed to load device details
+devicePingLog.chart.latency.avg=Avg
+devicePingLog.chart.latency.min=Min
+devicePingLog.chart.latency.max=Max
+devicePingLog.chart.availability.failAxis=Failures
+devicePingLog.chart.availability.successRateAxis=Success Rate %
+devicePingLog.chart.availability.failCount=Fail Count
+devicePingLog.chart.availability.successRate=Success Rate
+devicePingLog.systemDefaultPacketSize=System Default
+devicePingLog.status.ok=Normal
+devicePingLog.status.unstable=Unstable
+devicePingLog.status.timeout=Timeout
+devicePingLog.status.error=Error
+devicePingLog.status.noData=No Data
llm.logsTitle=LLM Call Logs
llm.logDetailTitle=Log Details
llm.logDetailPrefix=Log Details -
diff --git a/src/main/resources/i18n/zh-CN/legacy.properties b/src/main/resources/i18n/zh-CN/legacy.properties
index 2e5329b..ff78c24 100644
--- a/src/main/resources/i18n/zh-CN/legacy.properties
+++ b/src/main/resources/i18n/zh-CN/legacy.properties
@@ -161,3 +161,52 @@
鏂板绯荤粺閰嶇疆=鏂板绯荤粺閰嶇疆
淇敼绯荤粺閰嶇疆=淇敼绯荤粺閰嶇疆
璇︽儏绯荤粺閰嶇疆=璇︽儏绯荤粺閰嶇疆
+
+# Dashboard and device ping pages
+绯荤粺浠〃鐩�=绯荤粺浠〃鐩�
+璁惧缃戠粶鍒嗘瀽=璁惧缃戠粶鍒嗘瀽
+鐩戞帶鐢婚潰=鐩戞帶鐢婚潰
+鐩戞帶绯荤粺=鐩戞帶绯荤粺
+鍏ュ簱浠诲姟=鍏ュ簱浠诲姟
+鍑哄簱浠诲姟=鍑哄簱浠诲姟
+绉诲簱浠诲姟=绉诲簱浠诲姟
+鎵ц涓�=鎵ц涓�
+寰呬汉宸�=寰呬汉宸�
+宸插畬鎴�=宸插畬鎴�
+鏂板缓=鏂板缓
+鐢熸垚鍏ュ簱浠诲姟=鐢熸垚鍏ュ簱浠诲姟
+璁惧涓婅蛋=璁惧涓婅蛋
+璁惧鎼繍涓�=璁惧鎼繍涓�
+璁惧鎼繍瀹屾垚=璁惧鎼繍瀹屾垚
+鍏ュ簱寰呬汉宸ュ洖婊�=鍏ュ簱寰呬汉宸ュ洖婊�
+鍏ュ簱瀹屾垚=鍏ュ簱瀹屾垚
+鍏ュ簱搴撳瓨鏇存柊=鍏ュ簱搴撳瓨鏇存柊
+鐢熸垚鍑哄簱浠诲姟=鐢熸垚鍑哄簱浠诲姟
+绔欑偣杩愯涓�=绔欑偣杩愯涓�
+绔欑偣杩愯瀹屾垚=绔欑偣杩愯瀹屾垚
+鍑哄簱寰呬汉宸ュ洖婊�=鍑哄簱寰呬汉宸ュ洖婊�
+鍑哄簱瀹屾垚=鍑哄簱瀹屾垚
+鍑哄簱搴撳瓨鏇存柊=鍑哄簱搴撳瓨鏇存柊
+鐢熸垚绉诲簱浠诲姟=鐢熸垚绉诲簱浠诲姟
+绉诲簱寰呬汉宸ュ洖婊�=绉诲簱寰呬汉宸ュ洖婊�
+绉诲簱瀹屾垚=绉诲簱瀹屾垚
+杈撻�佺珯鐐�=杈撻�佺珯鐐�
+鍙敤=鍙敤
+宸茬鐢�=宸茬鐢�
+瓒呮椂=瓒呮椂
+娉㈠姩=娉㈠姩
+瓒呮椂/寮傚父=瓒呮椂/寮傚父
+鏈畾涔夌姸鎬�=鏈畾涔夌姸鎬�
+鍏ㄩ儴鎺㈡祴鍧囪秴鏃�=鍏ㄩ儴鎺㈡祴鍧囪秴鏃�
+鏆傛棤棰濆璇存槑=鏆傛棤棰濆璇存槑
+鎺㈡祴澶辫触=鎺㈡祴澶辫触
+璇锋眰瓒呮椂=璇锋眰瓒呮椂
+鎵句笉鍒颁富鏈�=鎵句笉鍒颁富鏈�
+鏃犳硶璁块棶鐩爣涓绘満=鏃犳硶璁块棶鐩爣涓绘満
+100.0% 涓㈠け=100.0% 涓㈠け
+regex\:^鐘舵��(\d+)$=鐘舵��$1
+regex\:^绔欑偣(\d+)$=绔欑偣$1
+regex\:^绔欑偣(\d+)\s*/\s*(.+)$=绔欑偣$1 / $2
+regex\:^鍫嗗灈鏈�#(\d+)$=鍫嗗灈鏈�#$1
+regex\:^鍙屽伐浣�#(\d+)$=鍙屽伐浣�#$1
+regex\:^RGV#(\d+)$=RGV#$1
diff --git a/src/main/resources/i18n/zh-CN/messages.properties b/src/main/resources/i18n/zh-CN/messages.properties
index 058262b..231d7ce 100644
--- a/src/main/resources/i18n/zh-CN/messages.properties
+++ b/src/main/resources/i18n/zh-CN/messages.properties
@@ -213,6 +213,194 @@
legacy.regex.stationDeviceLink=鐐瑰嚮鍒犻櫎鍏宠仈: 绔欑偣 {0} -> 璁惧 {1}
deviceLogs.visualizationPrefix=鏃ュ織鍙鍖� -
deviceLogs.downloadDialogTitle=鏂囦欢涓嬭浇涓�
+dashboard.title=绯荤粺浠〃鐩�
+dashboard.monitorView=鐩戞帶鐢婚潰
+dashboard.openMonitor=鎵撳紑鐩戞帶鐢婚潰
+dashboard.refreshNow=绔嬪嵆鍒锋柊
+dashboard.overviewKicker=鐘舵�佹瑙�
+dashboard.overviewNote=绯荤粺涓庡埛鏂拌妭濂�
+dashboard.systemStatusLabel=绯荤粺鐘舵��
+dashboard.systemRunning=杩愯涓�
+dashboard.systemPaused=宸叉殏鍋�
+dashboard.systemStatusDesc=WCS 涓绘湇鍔″綋鍓嶇姸鎬�
+dashboard.lastRefreshLabel=鏈�杩戝埛鏂�
+dashboard.lastRefreshDesc=鏈�杩戜竴娆¤仛鍚堟暟鎹敓鎴愭椂闂�
+dashboard.autoRefreshLabel=鑷姩鍒锋柊
+dashboard.autoRefreshValue={0}s 鍚庡埛鏂�
+dashboard.autoRefreshDesc=椤甸潰鑷姩鏇存柊鍊掕鏃�
+dashboard.coreMetricsKicker=鏍稿績鎸囨爣
+dashboard.coreMetricsNote=浠诲姟銆佽澶囦笌 AI 鎬昏
+dashboard.taskTotalLabel=浠诲姟鎬绘暟
+dashboard.taskTotalDesc=褰撳墠鎵ц涓� {0}
+dashboard.deviceOnlineLabel=鍦ㄧ嚎璁惧
+dashboard.deviceOnlineDesc=鎬昏澶� {0}锛屽憡璀� {1}
+dashboard.aiTokenTotalLabel=AI 绱 Tokens
+dashboard.aiTokenTotalDesc=鎸� AI 浼氳瘽绱缁熻
+dashboard.aiCallTotalLabel=LLM 璋冪敤娆℃暟
+dashboard.aiCallTotalDesc=鏈�杩戜竴杞繍琛屾儏鍐靛凡绾冲叆涓嬫柟 AI 鍖哄煙
+dashboard.taskPanelTitle=浠诲姟鎬佸娍
+dashboard.taskPanelDesc=浠庝换鍔$被鍨嬨�佹墽琛岄樁娈靛拰鏈�杩戞祦杞褰曞揩閫熷垽鏂綋鍓嶄綔涓氬帇鍔涖��
+dashboard.taskRunningLabel=鎵ц涓�
+dashboard.taskRunningHint=褰撳墠姝e湪娴佽浆鐨勪换鍔�
+dashboard.taskManualLabel=寰呬汉宸�
+dashboard.taskManualHint=闇�浜哄伐鍏虫敞鎴栧洖婊�
+dashboard.taskCompletedLabel=宸插畬鎴�
+dashboard.taskCompletedHint=宸茬粡瀹屾垚鎴栬惤璐�
+dashboard.taskNewLabel=鏂板缓
+dashboard.taskNewHint=鍒氳繘鍏ヨ皟搴︽祦绋�
+dashboard.taskDirectionChartTitle=浠诲姟绫诲瀷鍒嗗竷
+dashboard.taskStageChartTitle=浠诲姟闃舵姒傝
+dashboard.recentPanelTitle=鏈�杩戜换鍔�
+dashboard.recentPanelDesc=甯姪蹇�熷垽鏂换鍔℃槸鍚﹀爢绉�佹槸鍚﹁璁惧鎺ユ墜锛屼互鍙婃渶杩戠殑浠诲姟鐩爣浣嶇疆銆�
+dashboard.recentEmpty=鏆傛棤浠诲姟璁板綍
+dashboard.column.workNo=浠诲姟鍙�
+dashboard.column.taskType=浠诲姟绫诲瀷
+dashboard.column.status=鐘舵��
+dashboard.column.source=鏉ユ簮
+dashboard.column.target=鐩爣
+dashboard.column.device=鎵ц璁惧
+dashboard.column.barcode=鏉$爜
+dashboard.column.updateTime=鏈�杩戞洿鏂版椂闂�
+dashboard.aiPanelTitle=AI 杩愯鎯呭喌
+dashboard.aiPanelDesc=鏌ョ湅 AI 浼氳瘽绱 Tokens銆丩LM 璋冪敤閲忥紝浠ュ強璺敱鐨勫彲鐢ㄤ笌鍐峰嵈鐘舵�併��
+dashboard.availableRoutesTag=鍙敤璺敱 {0}
+dashboard.aiTokenCardLabel=绱 Tokens
+dashboard.aiTokenCardHint=Prompt + Completion
+dashboard.aiAskCountLabel=鎻愰棶杞
+dashboard.aiAskCountHint=AI 瀵硅瘽绱杞
+dashboard.aiLlmCallLabel=LLM 璋冪敤
+dashboard.aiLlmCallHint=鎴愬姛 {0} / 澶辫触 {1}
+dashboard.aiSessionCountLabel=浼氳瘽鏁�
+dashboard.aiSessionCountHint=鏈�杩戣皟鐢� {0}
+dashboard.aiRouteChartTitle=AI 璺敱鐘舵��
+dashboard.aiRouteDesc=妯″瀷 {0}锛屼紭鍏堢骇 {1}
+dashboard.aiRouteResult=鎴愬姛 {0} / 澶辫触 {1}
+dashboard.aiRouteLastUsed=鏈�杩戜娇鐢� {0}
+dashboard.aiRouteEmpty=鏆傛棤 AI 璺敱鏁版嵁
+dashboard.devicePanelTitle=璁惧鎬佸娍
+dashboard.devicePanelDesc=姹囨�昏緭閫佺珯鐐广�佸爢鍨涙満銆佸弻宸ヤ綅鍫嗗灈鏈轰笌 RGV 鐨勫湪绾裤�佸繖纰屽拰鍛婅鎯呭喌銆�
+dashboard.deviceOnlineRate=鍦ㄧ嚎鐜� {0}
+dashboard.deviceTotalLabel=璁惧鎬绘暟
+dashboard.deviceTotalHint=宸插惎鐢ㄩ厤缃澶�
+dashboard.deviceOnlineCardLabel=鍦ㄧ嚎璁惧
+dashboard.deviceOnlineCardHint=瀹炴椂杩為�氳澶囨暟閲�
+dashboard.deviceBusyLabel=蹇欑璁惧
+dashboard.deviceBusyHint=褰撳墠鎵胯浇浠诲姟鐨勮澶�
+dashboard.deviceAlarmLabel=鍛婅璁惧
+dashboard.deviceAlarmHint=鍚樆濉炴垨鎶ヨ鐘舵��
+dashboard.deviceTypeChartTitle=璁惧鍦ㄧ嚎鍒嗗竷
+dashboard.deviceTypeDesc=鍦ㄧ嚎 {0} / 鎬绘暟 {1}锛岀绾� {2}
+dashboard.deviceBusyTag=蹇欑 {0}
+dashboard.deviceAlarmTag=鍛婅 {0}
+dashboard.networkPanelDesc=姹囨�绘渶鏂� Ping 鏍锋湰鐨勮繛閫氭�с�佸欢杩熶笌寮傚父璁惧锛屽府鍔╁揩閫熷彂鐜扮綉缁滄尝鍔ㄣ��
+dashboard.networkAttentionTag=闇�鍏虫敞 {0}
+dashboard.networkViewDetail=鏌ョ湅鏄庣粏
+dashboard.networkOkLabel=姝e父
+dashboard.networkOkHint=鏈�鏂版牱鏈姸鎬� OK
+dashboard.networkUnstableLabel=娉㈠姩
+dashboard.networkUnstableHint=閮ㄥ垎鎺㈡祴鎴愬姛
+dashboard.networkOfflineLabel=瓒呮椂/寮傚父
+dashboard.networkNoDataLabel=鏆傛棤鏁版嵁
+dashboard.networkNoDataHint=鏆傛棤鏁版嵁 {0}
+dashboard.networkAvgLatencyLabel=骞冲潎寤惰繜
+dashboard.networkPeakLatencyHint=宄板�� {0}
+dashboard.networkStatusChartTitle=杩為�氱姸鎬佸垎甯�
+dashboard.networkAvgLatencyTag=骞冲潎 {0}
+dashboard.networkLatestSampleTag=鏈�杩戞牱鏈� {0}
+dashboard.networkHealthyTitle=褰撳墠缃戠粶鎺㈡祴绋冲畾
+dashboard.networkHealthyDesc=宸茬撼鍏� {0} 鍙拌澶囷紝鏈�杩戜竴杞湭鍙戠幇瓒呮椂鎴栨尝鍔ㄣ��
+dashboard.networkHealthyOk=姝e父 {0}
+dashboard.networkHealthyAvg=骞冲潎 {0}
+dashboard.networkHealthyPeak=宄板�� {0}
+dashboard.networkEmpty=鏆傛棤璁惧缃戠粶鏍锋湰
+dashboard.loadingTitle=姝e湪鍔犺浇浠〃鐩�
+dashboard.loadingDesc=姹囨�讳换鍔°�佽澶囦笌 AI 杩愯鏁版嵁锛岃绋嶅��...
+dashboard.loadFailed=浠〃鐩樻暟鎹姞杞藉け璐�
+dashboard.loadFailedDetail=浠〃鐩樻暟鎹姞杞藉け璐ワ紝璇锋鏌ユ帴鍙g姸鎬�
+dashboard.deviceOnlineLegend=鍦ㄧ嚎
+dashboard.deviceOfflineLegend=绂荤嚎
+dashboard.chartCenter.availableRoutes=鍙敤璺敱
+dashboard.chartCenter.attentionDevices=闇�鍏虫敞璁惧
+dashboard.systemDefaultPacketSize=绯荤粺榛樿
+dashboard.networkSampling=閲囨牱 {0} ms / 瓒呮椂 {1} ms / 姣忔牱鏈� {2} 娆� / 鍖呭ぇ灏� {3}
+dashboard.taskDirectionInbound=鍏ュ簱浠诲姟
+dashboard.taskDirectionOutbound=鍑哄簱浠诲姟
+dashboard.taskDirectionMove=绉诲簱浠诲姟
+dashboard.aiRouteStatusAvailable=鍙敤
+dashboard.aiRouteStatusCooling=鍐峰嵈涓�
+dashboard.aiRouteStatusDisabled=宸茬鐢�
+devicePingLog.title=璁惧缃戠粶鍒嗘瀽
+devicePingLog.pageMeta=鍖呭ぇ灏� {0}锛寋1}
+devicePingLog.summary.totalDevices=璁惧鎬绘暟
+devicePingLog.summary.totalDevicesHint=宸查厤缃� IP 鐨勮澶�
+devicePingLog.summary.ok=姝e父
+devicePingLog.summary.okHint=鏈�杩戞牱鏈姸鎬� OK
+devicePingLog.summary.unstable=娉㈠姩
+devicePingLog.summary.unstableHint=閮ㄥ垎鎺㈡祴鎴愬姛
+devicePingLog.summary.offline=瓒呮椂/寮傚父
+devicePingLog.summary.offlineHint=鏈�杩戞牱鏈笉鍙揪
+devicePingLog.summary.noData=鏆傛棤鏁版嵁
+devicePingLog.summary.noDataHint=杩樻病鏈夎惤鐩樻牱鏈�
+devicePingLog.summary.avgLatency=鏁翠綋骞冲潎寤惰繜
+devicePingLog.summary.peakLatency=宄板�� {0}
+devicePingLog.overviewTitle=璁惧鎬昏
+devicePingLog.refresh=鍒锋柊
+devicePingLog.filter.deviceType=璁惧绫诲瀷
+devicePingLog.filter.all=鍏ㄩ儴
+devicePingLog.filter.keyword=鍏抽敭瀛�
+devicePingLog.filter.keywordPlaceholder=璁惧鍙� / IP
+devicePingLog.overviewCount=鎬昏璁惧 {0} 鍙�
+devicePingLog.column.device=璁惧
+devicePingLog.column.status=鐘舵��
+devicePingLog.column.successRate=鎴愬姛鐜�
+devicePingLog.column.avg=骞冲潎
+devicePingLog.column.min=鏈�灏�
+devicePingLog.column.max=鏈�澶�
+devicePingLog.column.updateTime=鏇存柊鏃堕棿
+devicePingLog.column.message=璇存槑
+devicePingLog.column.action=鎿嶄綔
+devicePingLog.viewDetail=鏌ョ湅璇︽儏
+devicePingLog.detailTitle=璁惧璇︽儏
+devicePingLog.detailEmpty=浠庝笂鏂硅澶囨�昏閫夋嫨涓�鍙拌澶囨煡鐪嬬绾ф槑缁�
+devicePingLog.filter.timeRange=鏃堕棿鑼冨洿
+devicePingLog.filter.rangeSeparator=鑷�
+devicePingLog.filter.start=寮�濮�
+devicePingLog.filter.end=缁撴潫
+devicePingLog.quickRange.30m=30 鍒嗛挓
+devicePingLog.quickRange.1h=1 灏忔椂
+devicePingLog.quickRange.6h=6 灏忔椂
+devicePingLog.query=鏌ヨ
+devicePingLog.detail.status=鐘舵��
+devicePingLog.detail.packetSize=鍖呭ぇ灏�
+devicePingLog.detail.successRate=鎴愬姛鐜�
+devicePingLog.detail.avg=骞冲潎
+devicePingLog.detail.min=鏈�灏�
+devicePingLog.detail.max=鏈�澶�
+devicePingLog.chart.latencyTitle=寤惰繜
+devicePingLog.chart.empty=褰撳墠鑼冨洿鏆傛棤绉掔骇鏍锋湰
+devicePingLog.chart.availabilityTitle=鎴愬姛鐜� / 澶辫触娆℃暟
+devicePingLog.alertColumn.time=鏃堕棿
+devicePingLog.alertColumn.status=鐘舵��
+devicePingLog.alertColumn.message=璇存槑
+devicePingLog.samplingConfigText=閲囨牱 {0} ms / 瓒呮椂 {1} ms / 姣忔牱鏈� {2} 娆�
+devicePingLog.optionsLoadFailed=璁惧閰嶇疆鍔犺浇澶辫触
+devicePingLog.overviewLoadFailed=鎬昏鏁版嵁鍔犺浇澶辫触
+devicePingLog.selectTimeRange=璇烽�夋嫨鏃堕棿鑼冨洿
+devicePingLog.invalidDevice=璁惧淇℃伅鏃犳晥
+devicePingLog.detailLoadFailed=璁惧璇︽儏鍔犺浇澶辫触
+devicePingLog.chart.latency.avg=骞冲潎
+devicePingLog.chart.latency.min=鏈�灏�
+devicePingLog.chart.latency.max=鏈�澶�
+devicePingLog.chart.availability.failAxis=澶辫触
+devicePingLog.chart.availability.successRateAxis=鎴愬姛鐜�%
+devicePingLog.chart.availability.failCount=澶辫触娆℃暟
+devicePingLog.chart.availability.successRate=鎴愬姛鐜�
+devicePingLog.systemDefaultPacketSize=绯荤粺榛樿
+devicePingLog.status.ok=姝e父
+devicePingLog.status.unstable=娉㈠姩
+devicePingLog.status.timeout=瓒呮椂
+devicePingLog.status.error=寮傚父
+devicePingLog.status.noData=鏆傛棤鏁版嵁
llm.logsTitle=LLM璋冪敤鏃ュ織
llm.logDetailTitle=鏃ュ織璇︽儏
llm.logDetailPrefix=鏃ュ織璇︽儏 -
diff --git a/src/main/webapp/static/js/dashboard/dashboard.js b/src/main/webapp/static/js/dashboard/dashboard.js
index db6676a..42e63df 100644
--- a/src/main/webapp/static/js/dashboard/dashboard.js
+++ b/src/main/webapp/static/js/dashboard/dashboard.js
@@ -11,6 +11,7 @@
el: "#app",
data: function () {
return {
+ rawPayload: null,
loading: true,
refreshing: false,
countdown: REFRESH_SECONDS,
@@ -95,6 +96,12 @@
},
mounted: function () {
var self = this;
+ this.refreshDocumentTitle();
+ if (window.WCS_I18N && typeof window.WCS_I18N.onReady === "function") {
+ window.WCS_I18N.onReady(function () {
+ self.refreshLocalizedState();
+ });
+ }
this.$nextTick(function () {
self.initCharts();
self.loadDashboard(false);
@@ -108,6 +115,48 @@
this.disposeCharts();
},
methods: {
+ formatMessage: function (text, params) {
+ var list = Array.isArray(params) ? params : [params];
+ return String(text == null ? "" : text).replace(/\{(\d+)\}/g, function (match, index) {
+ return list[index] == null ? "" : list[index];
+ });
+ },
+ i18n: function (key, fallback, params) {
+ if (window.WCS_I18N && typeof window.WCS_I18N.t === "function") {
+ var translated = window.WCS_I18N.t(key, params);
+ if (translated && translated !== key) {
+ return translated;
+ }
+ }
+ return this.formatMessage(fallback || key, params);
+ },
+ translateLegacyText: function (text) {
+ if (text == null || text === "") {
+ return text;
+ }
+ if (window.WCS_I18N && typeof window.WCS_I18N.tl === "function") {
+ return window.WCS_I18N.tl(String(text));
+ }
+ return text;
+ },
+ getCurrentLocale: function () {
+ if (window.WCS_I18N && typeof window.WCS_I18N.getLocale === "function") {
+ return window.WCS_I18N.getLocale();
+ }
+ return "zh-CN";
+ },
+ refreshDocumentTitle: function () {
+ document.title = this.i18n("dashboard.title", "绯荤粺浠〃鐩�");
+ },
+ refreshLocalizedState: function () {
+ this.refreshDocumentTitle();
+ if (this.rawPayload) {
+ this.applyData(this.rawPayload);
+ return;
+ }
+ this.$forceUpdate();
+ this.updateCharts();
+ },
loadDashboard: function (manual) {
var self = this;
if (this.refreshing) {
@@ -124,11 +173,11 @@
self.countdown = REFRESH_SECONDS;
return;
}
- self.$message.error((res && res.msg) || "浠〃鐩樻暟鎹姞杞藉け璐�");
+ self.$message.error((res && res.msg) || self.i18n("dashboard.loadFailed", "浠〃鐩樻暟鎹姞杞藉け璐�"));
},
error: function () {
if (manual) {
- self.$message.error("浠〃鐩樻暟鎹姞杞藉け璐ワ紝璇锋鏌ユ帴鍙g姸鎬�");
+ self.$message.error(self.i18n("dashboard.loadFailedDetail", "浠〃鐩樻暟鎹姞杞藉け璐ワ紝璇锋鏌ユ帴鍙g姸鎬�"));
}
},
complete: function () {
@@ -138,12 +187,165 @@
});
},
applyData: function (payload) {
- this.overview = payload.overview || this.overview;
- this.tasks = payload.tasks || this.tasks;
- this.devices = payload.devices || this.devices;
- this.network = payload.network || this.network;
- this.ai = payload.ai || this.ai;
+ var tasks = payload && payload.tasks ? payload.tasks : {};
+ var devices = payload && payload.devices ? payload.devices : {};
+ var network = payload && payload.network ? payload.network : {};
+ var ai = payload && payload.ai ? payload.ai : {};
+
+ this.rawPayload = payload || {};
+ this.refreshDocumentTitle();
+ this.overview = payload && payload.overview ? payload.overview : this.overview;
+ this.tasks = {
+ overview: tasks.overview || this.tasks.overview,
+ directionStats: this.normalizeTaskDirectionMetrics(tasks.directionStats),
+ stageStats: this.normalizeTaskStageMetrics(tasks.stageStats),
+ statusStats: this.normalizeMetricList(tasks.statusStats),
+ recentTasks: this.normalizeRecentTasks(tasks.recentTasks)
+ };
+ this.devices = {
+ overview: devices.overview || this.devices.overview,
+ typeStats: this.normalizeDeviceTypeStats(devices.typeStats)
+ };
+ this.network = {
+ overview: network.overview || this.network.overview,
+ samplingConfig: network.samplingConfig || this.network.samplingConfig,
+ statusStats: this.normalizeNetworkStatusMetrics(network.statusStats),
+ focusDevices: this.normalizeFocusDevices(network.focusDevices)
+ };
+ this.ai = {
+ overview: ai.overview || this.ai.overview,
+ routeStats: this.normalizeAiRouteStats(ai.routeStats),
+ routeList: this.normalizeAiRouteList(ai.routeList)
+ };
this.updateCharts();
+ },
+ normalizeTaskDirectionMetrics: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ if (item && item.name === "鍏ュ簱浠诲姟") {
+ result.name = self.i18n("dashboard.taskDirectionInbound", "鍏ュ簱浠诲姟");
+ } else if (item && item.name === "鍑哄簱浠诲姟") {
+ result.name = self.i18n("dashboard.taskDirectionOutbound", "鍑哄簱浠诲姟");
+ } else if (item && item.name === "绉诲簱浠诲姟") {
+ result.name = self.i18n("dashboard.taskDirectionMove", "绉诲簱浠诲姟");
+ } else {
+ result.name = self.translateLegacyText(item && item.name);
+ }
+ return result;
+ });
+ },
+ normalizeTaskStageMetrics: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ if (item && item.name === "鎵ц涓�") {
+ result.name = self.i18n("dashboard.taskRunningLabel", "鎵ц涓�");
+ } else if (item && item.name === "寰呬汉宸�") {
+ result.name = self.i18n("dashboard.taskManualLabel", "寰呬汉宸�");
+ } else if (item && item.name === "宸插畬鎴�") {
+ result.name = self.i18n("dashboard.taskCompletedLabel", "宸插畬鎴�");
+ } else if (item && item.name === "鏂板缓") {
+ result.name = self.i18n("dashboard.taskNewLabel", "鏂板缓");
+ } else {
+ result.name = self.translateLegacyText(item && item.name);
+ }
+ return result;
+ });
+ },
+ normalizeMetricList: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ result.name = self.translateLegacyText(item && item.name);
+ return result;
+ });
+ },
+ normalizeRecentTasks: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ result.taskType = self.translateLegacyText(item && item.taskType);
+ result.status = self.translateLegacyText(item && item.status);
+ result.source = self.translateLegacyText(item && item.source);
+ result.target = self.translateLegacyText(item && item.target);
+ result.device = self.translateLegacyText(item && item.device);
+ return result;
+ });
+ },
+ normalizeAiRouteStats: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ if (item && item.name === "鍙敤") {
+ result.name = self.i18n("dashboard.aiRouteStatusAvailable", "鍙敤");
+ } else if (item && item.name === "鍐峰嵈涓�") {
+ result.name = self.i18n("dashboard.aiRouteStatusCooling", "鍐峰嵈涓�");
+ } else if (item && item.name === "宸茬鐢�") {
+ result.name = self.i18n("dashboard.aiRouteStatusDisabled", "宸茬鐢�");
+ } else {
+ result.name = self.translateLegacyText(item && item.name);
+ }
+ return result;
+ });
+ },
+ normalizeNetworkStatusMetrics: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ if (item && item.name === "姝e父") {
+ result.name = self.i18n("dashboard.networkOkLabel", "姝e父");
+ } else if (item && item.name === "娉㈠姩") {
+ result.name = self.i18n("dashboard.networkUnstableLabel", "娉㈠姩");
+ } else if (item && item.name === "瓒呮椂/寮傚父") {
+ result.name = self.i18n("dashboard.networkOfflineLabel", "瓒呮椂/寮傚父");
+ } else if (item && item.name === "鏆傛棤鏁版嵁") {
+ result.name = self.i18n("dashboard.networkNoDataLabel", "鏆傛棤鏁版嵁");
+ } else {
+ result.name = self.translateLegacyText(item && item.name);
+ }
+ return result;
+ });
+ },
+ normalizeDeviceTypeStats: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ result.name = self.translateLegacyText(item && item.name);
+ return result;
+ });
+ },
+ normalizeAiRouteList: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ if (result.statusType === "success") {
+ result.statusText = self.i18n("dashboard.aiRouteStatusAvailable", "鍙敤");
+ } else if (result.statusType === "warning") {
+ result.statusText = self.i18n("dashboard.aiRouteStatusCooling", "鍐峰嵈涓�");
+ } else {
+ result.statusText = self.i18n("dashboard.aiRouteStatusDisabled", "宸茬鐢�");
+ }
+ result.lastError = self.translateLegacyText(item && item.lastError);
+ return result;
+ });
+ },
+ normalizeFocusDevices: function (list) {
+ var self = this;
+ return cloneMetricList(list).map(function (item) {
+ var result = Object.assign({}, item);
+ if (result.statusType === "success") {
+ result.statusText = self.i18n("dashboard.networkOkLabel", "姝e父");
+ } else if (result.statusType === "warning") {
+ result.statusText = self.i18n("dashboard.networkUnstableLabel", "娉㈠姩");
+ } else if (result.statusType === "danger") {
+ result.statusText = self.i18n("dashboard.networkOfflineLabel", "瓒呮椂/寮傚父");
+ } else {
+ result.statusText = self.i18n("dashboard.networkNoDataLabel", "鏆傛棤鏁版嵁");
+ }
+ result.message = self.translateLegacyText(item && item.message);
+ return result;
+ });
},
initCharts: function () {
this.disposeCharts();
@@ -247,6 +449,8 @@
},
buildDeviceTypeOption: function () {
var data = cloneMetricList(this.devices.typeStats);
+ var onlineText = this.i18n("dashboard.deviceOnlineLegend", "鍦ㄧ嚎");
+ var offlineText = this.i18n("dashboard.deviceOfflineLegend", "绂荤嚎");
return {
color: ["#2fa38e", "#d8e2ec"],
legend: {
@@ -255,7 +459,7 @@
itemWidth: 10,
itemHeight: 10,
textStyle: { color: "#60778d", fontSize: 12 },
- data: ["鍦ㄧ嚎", "绂荤嚎"]
+ data: [onlineText, offlineText]
},
grid: {
left: 76,
@@ -282,7 +486,7 @@
axisLabel: { color: "#60778d", fontSize: 12 }
},
series: [{
- name: "鍦ㄧ嚎",
+ name: onlineText,
type: "bar",
stack: "device",
barWidth: 18,
@@ -291,7 +495,7 @@
borderRadius: [9, 0, 0, 9]
}
}, {
- name: "绂荤嚎",
+ name: offlineText,
type: "bar",
stack: "device",
barWidth: 18,
@@ -325,7 +529,7 @@
left: "center",
top: "55%",
style: {
- text: "鍙敤璺敱",
+ text: this.i18n("dashboard.chartCenter.availableRoutes", "鍙敤璺敱"),
fill: "#7c8fa4",
fontSize: 12
}
@@ -370,7 +574,7 @@
left: "center",
top: "54%",
style: {
- text: "闇�鍏虫敞璁惧",
+ text: this.i18n("dashboard.chartCenter.attentionDevices", "闇�鍏虫敞璁惧"),
fill: "#7c8fa4",
fontSize: 12
}
@@ -397,7 +601,7 @@
if (!isFinite(num)) {
return "0";
}
- return num.toLocaleString("zh-CN");
+ return num.toLocaleString(this.getCurrentLocale());
},
formatLatency: function (value) {
var num;
@@ -408,22 +612,33 @@
if (!isFinite(num)) {
return "--";
}
- return num.toLocaleString("zh-CN", { maximumFractionDigits: 2 }) + " ms";
+ return num.toLocaleString(this.getCurrentLocale(), { maximumFractionDigits: 2 }) + " ms";
+ },
+ formatPercentValue: function (value) {
+ var num = Number(value);
+ if (!isFinite(num)) {
+ return "--";
+ }
+ return num.toLocaleString(this.getCurrentLocale(), { maximumFractionDigits: 2 }) + "%";
},
formatPacketSize: function (value) {
var num = Number(value);
if (!isFinite(num) || num < 0) {
- return "绯荤粺榛樿";
+ return this.i18n("dashboard.systemDefaultPacketSize", "绯荤粺榛樿");
}
- return num + " B";
+ return num.toLocaleString(this.getCurrentLocale()) + " B";
},
displayText: function (value, fallback) {
return value == null || value === "" ? (fallback || "") : value;
},
networkSamplingText: function () {
var config = this.network && this.network.samplingConfig ? this.network.samplingConfig : {};
- return "閲囨牱 " + this.displayText(config.intervalMs, 0) + " ms / 瓒呮椂 " + this.displayText(config.timeoutMs, 0) +
- " ms / 姣忔牱鏈� " + this.displayText(config.probeCount, 0) + " 娆� / 鍖呭ぇ灏� " + this.formatPacketSize(config.packetSize);
+ return this.i18n("dashboard.networkSampling", "閲囨牱 {0} ms / 瓒呮椂 {1} ms / 姣忔牱鏈� {2} 娆� / 鍖呭ぇ灏� {3}", [
+ this.displayText(config.intervalMs, 0),
+ this.displayText(config.timeoutMs, 0),
+ this.displayText(config.probeCount, 0),
+ this.formatPacketSize(config.packetSize)
+ ]);
},
startAutoRefresh: function () {
var self = this;
@@ -549,10 +764,19 @@
window.open(targetMenu && targetMenu.url ? targetMenu.url : this.resolveAbsoluteViewPath(targetPath), "_blank");
},
openMonitor: function () {
- this.openParentMenuView("/views/watch/console.html", "鐩戞帶鐢婚潰", "鐩戞帶鐢婚潰", "鐩戞帶绯荤粺");
+ this.openParentMenuView(
+ "/views/watch/console.html",
+ this.i18n("dashboard.monitorView", "鐩戞帶鐢婚潰"),
+ this.translateLegacyText("鐩戞帶鐢婚潰"),
+ this.translateLegacyText("鐩戞帶绯荤粺")
+ );
},
openDevicePingAnalysis: function () {
- this.openParentMenuView("/views/devicePingLog/devicePingLog.html", "璁惧缃戠粶鍒嗘瀽", "璁惧缃戠粶鍒嗘瀽");
+ this.openParentMenuView(
+ "/views/devicePingLog/devicePingLog.html",
+ this.i18n("devicePingLog.title", "璁惧缃戠粶鍒嗘瀽"),
+ this.translateLegacyText("璁惧缃戠粶鍒嗘瀽")
+ );
}
}
});
diff --git a/src/main/webapp/static/js/devicePingLog/devicePingLog.js b/src/main/webapp/static/js/devicePingLog/devicePingLog.js
index 5e002d9..60b73c3 100644
--- a/src/main/webapp/static/js/devicePingLog/devicePingLog.js
+++ b/src/main/webapp/static/js/devicePingLog/devicePingLog.js
@@ -49,6 +49,9 @@
el: "#app",
data: function () {
return {
+ lastOptionsData: null,
+ lastOverviewData: null,
+ lastTrendData: null,
overviewLoading: false,
detailLoading: false,
devices: [],
@@ -111,11 +114,21 @@
},
samplingConfigText: function () {
var config = this.samplingConfig || createEmptySamplingConfig();
- return "閲囨牱 " + config.intervalMs + " ms / 瓒呮椂 " + config.timeoutMs + " ms / 姣忔牱鏈� " + config.probeCount + " 娆�";
+ return this.i18n("devicePingLog.samplingConfigText", "閲囨牱 {0} ms / 瓒呮椂 {1} ms / 姣忔牱鏈� {2} 娆�", [
+ this.formatNumber(config.intervalMs),
+ this.formatNumber(config.timeoutMs),
+ this.formatNumber(config.probeCount)
+ ]);
}
},
mounted: function () {
var self = this;
+ this.refreshDocumentTitle();
+ if (window.WCS_I18N && typeof window.WCS_I18N.onReady === "function") {
+ window.WCS_I18N.onReady(function () {
+ self.refreshLocalizedState();
+ });
+ }
this.$nextTick(function () {
self.loadOptions();
self.loadOverview();
@@ -132,6 +145,126 @@
this.disposeCharts();
},
methods: {
+ formatMessage: function (text, params) {
+ var list = Array.isArray(params) ? params : [params];
+ return String(text == null ? "" : text).replace(/\{(\d+)\}/g, function (match, index) {
+ return list[index] == null ? "" : list[index];
+ });
+ },
+ i18n: function (key, fallback, params) {
+ if (window.WCS_I18N && typeof window.WCS_I18N.t === "function") {
+ var translated = window.WCS_I18N.t(key, params);
+ if (translated && translated !== key) {
+ return translated;
+ }
+ }
+ return this.formatMessage(fallback || key, params);
+ },
+ translateLegacyText: function (text) {
+ if (text == null || text === "") {
+ return text;
+ }
+ if (window.WCS_I18N && typeof window.WCS_I18N.tl === "function") {
+ return window.WCS_I18N.tl(String(text));
+ }
+ return text;
+ },
+ getCurrentLocale: function () {
+ if (window.WCS_I18N && typeof window.WCS_I18N.getLocale === "function") {
+ return window.WCS_I18N.getLocale();
+ }
+ return "zh-CN";
+ },
+ refreshDocumentTitle: function () {
+ document.title = this.i18n("devicePingLog.title", "璁惧缃戠粶鍒嗘瀽");
+ },
+ refreshLocalizedState: function () {
+ this.refreshDocumentTitle();
+ if (this.lastOptionsData) {
+ this.applyOptionsData(this.lastOptionsData);
+ }
+ if (this.lastOverviewData) {
+ this.applyOverviewData(this.lastOverviewData);
+ }
+ if (this.lastTrendData) {
+ this.applyTrendData(this.lastTrendData);
+ } else {
+ this.$forceUpdate();
+ this.updateCharts();
+ }
+ },
+ resolveStatusText: function (status) {
+ var code = status == null ? "" : String(status).toUpperCase();
+ if (code === "OK") {
+ return this.i18n("devicePingLog.status.ok", "姝e父");
+ }
+ if (code === "UNSTABLE") {
+ return this.i18n("devicePingLog.status.unstable", "娉㈠姩");
+ }
+ if (code === "TIMEOUT") {
+ return this.i18n("devicePingLog.status.timeout", "瓒呮椂");
+ }
+ if (code === "ERROR") {
+ return this.i18n("devicePingLog.status.error", "寮傚父");
+ }
+ if (code === "NO_DATA") {
+ return this.i18n("devicePingLog.status.noData", "鏆傛棤鏁版嵁");
+ }
+ if (!code) {
+ return "--";
+ }
+ return this.translateLegacyText(status);
+ },
+ applyOptionsData: function (data) {
+ this.lastOptionsData = data || {};
+ this.refreshDocumentTitle();
+ this.devices = (data && data.devices) || [];
+ this.availableDays = (data && data.days) || [];
+ this.samplingConfig = Object.assign(createEmptySamplingConfig(), data && data.samplingConfig ? data.samplingConfig : {});
+ },
+ applyOverviewData: function (data) {
+ this.lastOverviewData = data || {};
+ this.overviewSummary = Object.assign(createEmptyOverviewSummary(), data && data.summary ? data.summary : {});
+ this.overviewRows = this.normalizeOverviewRows(data && data.devices ? data.devices : []);
+ },
+ applyTrendData: function (data) {
+ this.lastTrendData = data || {};
+ this.detailSummary = this.normalizeDetailSummary(data && data.summary ? data.summary : {});
+ this.series = (data && data.series) || [];
+ this.alerts = this.normalizeAlerts(data && data.alerts ? data.alerts : []);
+ this.updateCharts();
+ },
+ normalizeOverviewRows: function (rows) {
+ var self = this;
+ return (rows || []).map(function (item) {
+ var result = Object.assign({}, item);
+ result.statusText = self.resolveStatusText(item && item.status);
+ result.message = self.translateLegacyText(item && item.message);
+ result.label = self.translateLegacyText(item && item.label);
+ return result;
+ });
+ },
+ normalizeDetailSummary: function (summary) {
+ var result = Object.assign(createEmptyDetailSummary(), summary || {});
+ result.latestStatus = this.resolveStatusText(result.latestStatus);
+ return result;
+ },
+ normalizeAlerts: function (alerts) {
+ var self = this;
+ return (alerts || []).map(function (item) {
+ var result = Object.assign({}, item);
+ result.status = self.resolveStatusText(item && item.status);
+ result.message = self.translateLegacyText(item && item.message);
+ return result;
+ });
+ },
+ formatNumber: function (value) {
+ var num = Number(value || 0);
+ if (!isFinite(num)) {
+ return "0";
+ }
+ return num.toLocaleString(this.getCurrentLocale());
+ },
loadOptions: function () {
var self = this;
$.ajax({
@@ -141,15 +274,13 @@
success: function (res) {
if (res && res.code === 200) {
var data = res.data || {};
- self.devices = data.devices || [];
- self.availableDays = data.days || [];
- self.samplingConfig = Object.assign(createEmptySamplingConfig(), data.samplingConfig || {});
+ self.applyOptionsData(data);
return;
}
- self.$message.error((res && res.msg) || "璁惧閰嶇疆鍔犺浇澶辫触");
+ self.$message.error((res && res.msg) || self.i18n("devicePingLog.optionsLoadFailed", "璁惧閰嶇疆鍔犺浇澶辫触"));
},
error: function () {
- self.$message.error("璁惧閰嶇疆鍔犺浇澶辫触");
+ self.$message.error(self.i18n("devicePingLog.optionsLoadFailed", "璁惧閰嶇疆鍔犺浇澶辫触"));
}
});
},
@@ -162,19 +293,17 @@
method: "GET",
success: function (res) {
if (res && res.code === 200) {
- var data = res.data || {};
- self.overviewSummary = Object.assign(createEmptyOverviewSummary(), data.summary || {});
- self.overviewRows = data.devices || [];
+ self.applyOverviewData(res.data || {});
return;
}
self.overviewSummary = createEmptyOverviewSummary();
self.overviewRows = [];
- self.$message.error((res && res.msg) || "鎬昏鏁版嵁鍔犺浇澶辫触");
+ self.$message.error((res && res.msg) || self.i18n("devicePingLog.overviewLoadFailed", "鎬昏鏁版嵁鍔犺浇澶辫触"));
},
error: function () {
self.overviewSummary = createEmptyOverviewSummary();
self.overviewRows = [];
- self.$message.error("鎬昏鏁版嵁鍔犺浇澶辫触");
+ self.$message.error(self.i18n("devicePingLog.overviewLoadFailed", "鎬昏鏁版嵁鍔犺浇澶辫触"));
},
complete: function () {
self.overviewLoading = false;
@@ -204,12 +333,12 @@
return;
}
if (!this.detailFilters.range || this.detailFilters.range.length !== 2) {
- this.$message.warning("璇烽�夋嫨鏃堕棿鑼冨洿");
+ this.$message.warning(this.i18n("devicePingLog.selectTimeRange", "璇烽�夋嫨鏃堕棿鑼冨洿"));
return;
}
var parts = this.detailFilters.deviceKey.split("#");
if (parts.length !== 2) {
- this.$message.warning("璁惧淇℃伅鏃犳晥");
+ this.$message.warning(this.i18n("devicePingLog.invalidDevice", "璁惧淇℃伅鏃犳晥"));
return;
}
var self = this;
@@ -226,25 +355,21 @@
},
success: function (res) {
if (res && res.code === 200) {
- var data = res.data || {};
- self.detailSummary = Object.assign(createEmptyDetailSummary(), data.summary || {});
- self.series = data.series || [];
- self.alerts = data.alerts || [];
- self.updateCharts();
+ self.applyTrendData(res.data || {});
return;
}
self.detailSummary = createEmptyDetailSummary();
self.series = [];
self.alerts = [];
self.updateCharts();
- self.$message.error((res && res.msg) || "璁惧璇︽儏鍔犺浇澶辫触");
+ self.$message.error((res && res.msg) || self.i18n("devicePingLog.detailLoadFailed", "璁惧璇︽儏鍔犺浇澶辫触"));
},
error: function () {
self.detailSummary = createEmptyDetailSummary();
self.series = [];
self.alerts = [];
self.updateCharts();
- self.$message.error("璁惧璇︽儏鍔犺浇澶辫触");
+ self.$message.error(self.i18n("devicePingLog.detailLoadFailed", "璁惧璇︽儏鍔犺浇澶辫触"));
},
complete: function () {
self.detailLoading = false;
@@ -336,21 +461,21 @@
bottom: 10
}],
series: [{
- name: "骞冲潎",
+ name: this.i18n("devicePingLog.chart.latency.avg", "骞冲潎"),
type: "line",
showSymbol: false,
sampling: "lttb",
data: this.series.map(function (item) { return item.avgLatencyMs; }),
lineStyle: { width: 2 }
}, {
- name: "鏈�灏�",
+ name: this.i18n("devicePingLog.chart.latency.min", "鏈�灏�"),
type: "line",
showSymbol: false,
sampling: "lttb",
data: this.series.map(function (item) { return item.minLatencyMs; }),
lineStyle: { width: 1.5 }
}, {
- name: "鏈�澶�",
+ name: this.i18n("devicePingLog.chart.latency.max", "鏈�澶�"),
type: "line",
showSymbol: false,
sampling: "lttb",
@@ -389,14 +514,14 @@
},
yAxis: [{
type: "value",
- name: "澶辫触",
+ name: this.i18n("devicePingLog.chart.availability.failAxis", "澶辫触"),
splitLine: { lineStyle: { color: "#edf2f7" } },
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: "#7f92a5" }
}, {
type: "value",
- name: "鎴愬姛鐜�%",
+ name: this.i18n("devicePingLog.chart.availability.successRateAxis", "鎴愬姛鐜�%"),
min: 0,
max: 100,
splitLine: { show: false },
@@ -412,7 +537,7 @@
bottom: 10
}],
series: [{
- name: "澶辫触娆℃暟",
+ name: this.i18n("devicePingLog.chart.availability.failCount", "澶辫触娆℃暟"),
type: "bar",
yAxisIndex: 0,
barMaxWidth: 18,
@@ -421,7 +546,7 @@
borderRadius: [6, 6, 0, 0]
}
}, {
- name: "鎴愬姛鐜�",
+ name: this.i18n("devicePingLog.chart.availability.successRate", "鎴愬姛鐜�"),
type: "line",
yAxisIndex: 1,
showSymbol: false,
@@ -444,22 +569,33 @@
return "offline";
},
formatLatency: function (value) {
+ var num;
if (value === null || value === undefined || value === "") {
return "--";
}
- return value + " ms";
+ num = Number(value);
+ if (!isFinite(num)) {
+ return "--";
+ }
+ return num.toLocaleString(this.getCurrentLocale(), { maximumFractionDigits: 2 }) + " ms";
},
formatPercent: function (value) {
+ var num;
if (value === null || value === undefined || value === "") {
return "--";
}
- return value + "%";
+ num = Number(value);
+ if (!isFinite(num)) {
+ return "--";
+ }
+ return num.toLocaleString(this.getCurrentLocale(), { maximumFractionDigits: 2 }) + "%";
},
formatPacketSize: function (value) {
- if (value === null || value === undefined || value === "" || value < 0) {
- return "绯荤粺榛樿";
+ var num = Number(value);
+ if (!isFinite(num) || num < 0) {
+ return this.i18n("devicePingLog.systemDefaultPacketSize", "绯荤粺榛樿");
}
- return value + " B";
+ return num.toLocaleString(this.getCurrentLocale()) + " B";
}
}
});
diff --git a/src/main/webapp/views/dashboard/dashboard.html b/src/main/webapp/views/dashboard/dashboard.html
index 3c2b87e..1a806a2 100644
--- a/src/main/webapp/views/dashboard/dashboard.html
+++ b/src/main/webapp/views/dashboard/dashboard.html
@@ -713,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>
@@ -782,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>
@@ -833,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>
@@ -844,15 +844,15 @@
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>
@@ -861,37 +861,37 @@
<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>
+ <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">鍙敤璺敱 {{ formatNumber(ai.overview.availableRouteCount) }}</el-tag>
+ <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">绱 Tokens</div>
+ <div class="mini-label">{{ i18n('dashboard.aiTokenCardLabel', '绱 Tokens') }}</div>
<div class="mini-value">{{ formatNumber(ai.overview.tokenTotal) }}</div>
- <div class="mini-hint">Prompt + Completion</div>
+ <div class="mini-hint">{{ i18n('dashboard.aiTokenCardHint', 'Prompt + Completion') }}</div>
</div>
<div class="mini-card">
- <div class="mini-label">鎻愰棶杞</div>
+ <div class="mini-label">{{ i18n('dashboard.aiAskCountLabel', '鎻愰棶杞') }}</div>
<div class="mini-value">{{ formatNumber(ai.overview.askCount) }}</div>
- <div class="mini-hint">AI 瀵硅瘽绱杞</div>
+ <div class="mini-hint">{{ i18n('dashboard.aiAskCountHint', 'AI 瀵硅瘽绱杞') }}</div>
</div>
<div class="mini-card">
- <div class="mini-label">LLM 璋冪敤</div>
+ <div class="mini-label">{{ i18n('dashboard.aiLlmCallLabel', '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-hint">{{ i18n('dashboard.aiLlmCallHint', '鎴愬姛 {0} / 澶辫触 {1}', [formatNumber(ai.overview.successCallTotal), formatNumber(ai.overview.failCallTotal)]) }}</div>
</div>
<div class="mini-card">
- <div class="mini-label">浼氳瘽鏁�</div>
+ <div class="mini-label">{{ i18n('dashboard.aiSessionCountLabel', '浼氳瘽鏁�') }}</div>
<div class="mini-value">{{ formatNumber(ai.overview.sessionCount) }}</div>
- <div class="mini-hint">鏈�杩戣皟鐢� {{ displayText(ai.overview.lastCallTime, '-') }}</div>
+ <div class="mini-hint">{{ i18n('dashboard.aiSessionCountHint', '鏈�杩戣皟鐢� {0}', [displayText(ai.overview.lastCallTime, '-')]) }}</div>
</div>
</div>
<div class="chart-card">
- <div class="chart-title">AI 璺敱鐘舵��</div>
+ <div class="chart-title">{{ i18n('dashboard.aiRouteChartTitle', 'AI 璺敱鐘舵��') }}</div>
<div ref="aiRouteChart" class="ai-chart-box"></div>
</div>
@@ -899,17 +899,17 @@
<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">妯″瀷 {{ displayText(route.model, '-') }}锛屼紭鍏堢骇 {{ displayText(route.priority, '-') }}</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">鎴愬姛 {{ formatNumber(route.successCount) }} / 澶辫触 {{ formatNumber(route.failCount) }}</div>
- <div class="route-extra">鏈�杩戜娇鐢� {{ displayText(route.lastUsedTime, '-') }}</div>
+ <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="鏆傛棤 AI 璺敱鏁版嵁"></el-empty>
+ <el-empty v-else :description="i18n('dashboard.aiRouteEmpty', '鏆傛棤 AI 璺敱鏁版嵁')"></el-empty>
</section>
</div>
@@ -918,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>
@@ -956,11 +956,11 @@
<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>
@@ -970,42 +970,42 @@
<div class="panel-header">
<div>
<div class="panel-kicker">Network</div>
- <h2 class="panel-title">璁惧缃戠粶鍒嗘瀽</h2>
- <div class="panel-desc">姹囨�绘渶鏂� Ping 鏍锋湰鐨勮繛閫氭�с�佸欢杩熶笌寮傚父璁惧锛屽府鍔╁揩閫熷彂鐜扮綉缁滄尝鍔ㄣ��</div>
+ <h2 class="panel-title">{{ i18n('devicePingLog.title', '璁惧缃戠粶鍒嗘瀽') }}</h2>
+ <div class="panel-desc">{{ i18n('dashboard.networkPanelDesc', '姹囨�绘渶鏂� Ping 鏍锋湰鐨勮繛閫氭�с�佸欢杩熶笌寮傚父璁惧锛屽府鍔╁揩閫熷彂鐜扮綉缁滄尝鍔ㄣ��') }}</div>
</div>
<div class="panel-actions">
<el-tag size="small" :type="network.overview.attentionDevices > 0 ? 'warning' : 'success'">
- 闇�鍏虫敞 {{ formatNumber(network.overview.attentionDevices) }}
+ {{ i18n('dashboard.networkAttentionTag', '闇�鍏虫敞 {0}', [formatNumber(network.overview.attentionDevices)]) }}
</el-tag>
- <el-button size="mini" plain @click="openDevicePingAnalysis">鏌ョ湅鏄庣粏</el-button>
+ <el-button size="mini" plain @click="openDevicePingAnalysis">{{ i18n('dashboard.networkViewDetail', '鏌ョ湅鏄庣粏') }}</el-button>
</div>
</div>
<div class="mini-grid">
<div class="mini-card network-mini-ok">
- <div class="mini-label">姝e父</div>
+ <div class="mini-label">{{ i18n('dashboard.networkOkLabel', '姝e父') }}</div>
<div class="mini-value">{{ formatNumber(network.overview.okDevices) }}</div>
- <div class="mini-hint">鏈�鏂版牱鏈姸鎬� OK</div>
+ <div class="mini-hint">{{ i18n('dashboard.networkOkHint', '鏈�鏂版牱鏈姸鎬� OK') }}</div>
</div>
<div class="mini-card network-mini-warning">
- <div class="mini-label">娉㈠姩</div>
+ <div class="mini-label">{{ i18n('dashboard.networkUnstableLabel', '娉㈠姩') }}</div>
<div class="mini-value">{{ formatNumber(network.overview.unstableDevices) }}</div>
- <div class="mini-hint">閮ㄥ垎鎺㈡祴鎴愬姛</div>
+ <div class="mini-hint">{{ i18n('dashboard.networkUnstableHint', '閮ㄥ垎鎺㈡祴鎴愬姛') }}</div>
</div>
<div class="mini-card network-mini-offline">
- <div class="mini-label">瓒呮椂/寮傚父</div>
+ <div class="mini-label">{{ i18n('dashboard.networkOfflineLabel', '瓒呮椂/寮傚父') }}</div>
<div class="mini-value">{{ formatNumber(network.overview.offlineDevices) }}</div>
- <div class="mini-hint">鏆傛棤鏁版嵁 {{ formatNumber(network.overview.noDataDevices) }}</div>
+ <div class="mini-hint">{{ i18n('dashboard.networkNoDataHint', '鏆傛棤鏁版嵁 {0}', [formatNumber(network.overview.noDataDevices)]) }}</div>
</div>
<div class="mini-card network-mini-latency">
- <div class="mini-label">骞冲潎寤惰繜</div>
+ <div class="mini-label">{{ i18n('dashboard.networkAvgLatencyLabel', '骞冲潎寤惰繜') }}</div>
<div class="mini-value">{{ formatLatency(network.overview.avgLatencyMs) }}</div>
- <div class="mini-hint">宄板�� {{ formatLatency(network.overview.maxLatencyMs) }}</div>
+ <div class="mini-hint">{{ i18n('dashboard.networkPeakLatencyHint', '宄板�� {0}', [formatLatency(network.overview.maxLatencyMs)]) }}</div>
</div>
</div>
<div class="chart-card">
- <div class="chart-title">杩為�氱姸鎬佸垎甯�</div>
+ <div class="chart-title">{{ i18n('dashboard.networkStatusChartTitle', '杩為�氱姸鎬佸垎甯�') }}</div>
<div class="chart-subtitle">{{ networkSamplingText() }}</div>
<div ref="networkStatusChart" class="network-chart-box"></div>
</div>
@@ -1019,23 +1019,23 @@
</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 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>
<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 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">姝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 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="鏆傛棤璁惧缃戠粶鏍锋湰"></el-empty>
+ <el-empty v-else :description="i18n('dashboard.networkEmpty', '鏆傛棤璁惧缃戠粶鏍锋湰')"></el-empty>
</section>
</div>
</div>
@@ -1043,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>
@@ -1054,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?v=20260317-dashboard-network-focus"></script>
+<script type="text/javascript" src="../../static/js/dashboard/dashboard.js?v=20260317-dashboard-network-i18n"></script>
</body>
</html>
diff --git a/src/main/webapp/views/devicePingLog/devicePingLog.html b/src/main/webapp/views/devicePingLog/devicePingLog.html
index 6e8b7aa..09fdb60 100644
--- a/src/main/webapp/views/devicePingLog/devicePingLog.html
+++ b/src/main/webapp/views/devicePingLog/devicePingLog.html
@@ -275,106 +275,106 @@
<body>
<div id="app" v-cloak class="page-shell">
<div class="page-head">
- <div class="page-title">璁惧缃戠粶鍒嗘瀽</div>
- <div class="page-meta">鍖呭ぇ灏� {{ formatPacketSize(samplingConfig.packetSize) }}锛寋{ samplingConfigText }}</div>
+ <div class="page-title">{{ i18n('devicePingLog.title', '璁惧缃戠粶鍒嗘瀽') }}</div>
+ <div class="page-meta">{{ i18n('devicePingLog.pageMeta', '鍖呭ぇ灏� {0}锛寋1}', [formatPacketSize(samplingConfig.packetSize), samplingConfigText]) }}</div>
</div>
<section class="summary-grid">
<div class="summary-card">
- <div class="summary-label">璁惧鎬绘暟</div>
- <div class="summary-value">{{ overviewSummary.totalDevices }}</div>
- <div class="summary-sub">宸查厤缃� IP 鐨勮澶�</div>
+ <div class="summary-label">{{ i18n('devicePingLog.summary.totalDevices', '璁惧鎬绘暟') }}</div>
+ <div class="summary-value">{{ formatNumber(overviewSummary.totalDevices) }}</div>
+ <div class="summary-sub">{{ i18n('devicePingLog.summary.totalDevicesHint', '宸查厤缃� IP 鐨勮澶�') }}</div>
</div>
<div class="summary-card">
- <div class="summary-label">姝e父</div>
- <div class="summary-value">{{ overviewSummary.okDevices }}</div>
- <div class="summary-sub">鏈�杩戞牱鏈姸鎬� OK</div>
+ <div class="summary-label">{{ i18n('devicePingLog.summary.ok', '姝e父') }}</div>
+ <div class="summary-value">{{ formatNumber(overviewSummary.okDevices) }}</div>
+ <div class="summary-sub">{{ i18n('devicePingLog.summary.okHint', '鏈�杩戞牱鏈姸鎬� OK') }}</div>
</div>
<div class="summary-card">
- <div class="summary-label">娉㈠姩</div>
- <div class="summary-value">{{ overviewSummary.unstableDevices }}</div>
- <div class="summary-sub">閮ㄥ垎鎺㈡祴鎴愬姛</div>
+ <div class="summary-label">{{ i18n('devicePingLog.summary.unstable', '娉㈠姩') }}</div>
+ <div class="summary-value">{{ formatNumber(overviewSummary.unstableDevices) }}</div>
+ <div class="summary-sub">{{ i18n('devicePingLog.summary.unstableHint', '閮ㄥ垎鎺㈡祴鎴愬姛') }}</div>
</div>
<div class="summary-card">
- <div class="summary-label">瓒呮椂/寮傚父</div>
- <div class="summary-value">{{ overviewSummary.offlineDevices }}</div>
- <div class="summary-sub">鏈�杩戞牱鏈笉鍙揪</div>
+ <div class="summary-label">{{ i18n('devicePingLog.summary.offline', '瓒呮椂/寮傚父') }}</div>
+ <div class="summary-value">{{ formatNumber(overviewSummary.offlineDevices) }}</div>
+ <div class="summary-sub">{{ i18n('devicePingLog.summary.offlineHint', '鏈�杩戞牱鏈笉鍙揪') }}</div>
</div>
<div class="summary-card">
- <div class="summary-label">鏆傛棤鏁版嵁</div>
- <div class="summary-value">{{ overviewSummary.noDataDevices }}</div>
- <div class="summary-sub">杩樻病鏈夎惤鐩樻牱鏈�</div>
+ <div class="summary-label">{{ i18n('devicePingLog.summary.noData', '鏆傛棤鏁版嵁') }}</div>
+ <div class="summary-value">{{ formatNumber(overviewSummary.noDataDevices) }}</div>
+ <div class="summary-sub">{{ i18n('devicePingLog.summary.noDataHint', '杩樻病鏈夎惤鐩樻牱鏈�') }}</div>
</div>
<div class="summary-card">
- <div class="summary-label">鏁翠綋骞冲潎寤惰繜</div>
+ <div class="summary-label">{{ i18n('devicePingLog.summary.avgLatency', '鏁翠綋骞冲潎寤惰繜') }}</div>
<div class="summary-value">{{ formatLatency(overviewSummary.avgLatencyMs) }}</div>
- <div class="summary-sub">宄板�� {{ formatLatency(overviewSummary.maxLatencyMs) }}</div>
+ <div class="summary-sub">{{ i18n('devicePingLog.summary.peakLatency', '宄板�� {0}', [formatLatency(overviewSummary.maxLatencyMs)]) }}</div>
</div>
</section>
<section class="panel">
<div class="panel-head">
- <div class="panel-title">璁惧鎬昏</div>
- <el-button size="small" :loading="overviewLoading" @click="loadOverview">鍒锋柊</el-button>
+ <div class="panel-title">{{ i18n('devicePingLog.overviewTitle', '璁惧鎬昏') }}</div>
+ <el-button size="small" :loading="overviewLoading" @click="loadOverview">{{ i18n('devicePingLog.refresh', '鍒锋柊') }}</el-button>
</div>
<div class="panel-body">
<div class="toolbar">
<el-form :inline="true" size="small" :model="overviewFilters" style="margin-bottom: -18px;">
- <el-form-item label="璁惧绫诲瀷">
+ <el-form-item :label="i18n('devicePingLog.filter.deviceType', '璁惧绫诲瀷')">
<el-select v-model="overviewFilters.deviceType" clearable style="width: 120px;">
- <el-option label="鍏ㄩ儴" value=""></el-option>
+ <el-option :label="i18n('devicePingLog.filter.all', '鍏ㄩ儴')" value=""></el-option>
<el-option label="Crn" value="Crn"></el-option>
<el-option label="DualCrn" value="DualCrn"></el-option>
<el-option label="Devp" value="Devp"></el-option>
<el-option label="Rgv" value="Rgv"></el-option>
</el-select>
</el-form-item>
- <el-form-item label="鍏抽敭瀛�">
- <el-input v-model.trim="overviewFilters.keyword" clearable style="width: 220px;" placeholder="璁惧鍙� / IP"></el-input>
+ <el-form-item :label="i18n('devicePingLog.filter.keyword', '鍏抽敭瀛�')">
+ <el-input v-model.trim="overviewFilters.keyword" clearable style="width: 220px;" :placeholder="i18n('devicePingLog.filter.keywordPlaceholder', '璁惧鍙� / IP')"></el-input>
</el-form-item>
</el-form>
<div class="toolbar-right">
- <span class="page-meta">鎬昏璁惧 {{ filteredOverviewRows.length }} 鍙�</span>
+ <span class="page-meta">{{ i18n('devicePingLog.overviewCount', '鎬昏璁惧 {0} 鍙�', [formatNumber(filteredOverviewRows.length)]) }}</span>
</div>
</div>
<el-table :data="filteredOverviewRows" border stripe size="mini" v-loading="overviewLoading" style="width: 100%;">
- <el-table-column label="璁惧" min-width="170">
+ <el-table-column :label="i18n('devicePingLog.column.device', '璁惧')" min-width="170">
<template slot-scope="scope">
{{ scope.row.deviceType }}-{{ scope.row.deviceNo }}
</template>
</el-table-column>
<el-table-column prop="ip" label="IP" min-width="150"></el-table-column>
- <el-table-column label="鐘舵��" width="110" align="center">
+ <el-table-column :label="i18n('devicePingLog.column.status', '鐘舵��')" width="110" align="center">
<template slot-scope="scope">
<span class="status-chip" :class="statusClass(scope.row.status)">{{ scope.row.statusText }}</span>
</template>
</el-table-column>
- <el-table-column label="鎴愬姛鐜�" width="90" align="right">
+ <el-table-column :label="i18n('devicePingLog.column.successRate', '鎴愬姛鐜�')" width="90" align="right">
<template slot-scope="scope">
{{ formatPercent(scope.row.successRate) }}
</template>
</el-table-column>
- <el-table-column label="骞冲潎" width="90" align="right">
+ <el-table-column :label="i18n('devicePingLog.column.avg', '骞冲潎')" width="90" align="right">
<template slot-scope="scope">
{{ formatLatency(scope.row.avgLatencyMs) }}
</template>
</el-table-column>
- <el-table-column label="鏈�灏�" width="90" align="right">
+ <el-table-column :label="i18n('devicePingLog.column.min', '鏈�灏�')" width="90" align="right">
<template slot-scope="scope">
{{ formatLatency(scope.row.minLatencyMs) }}
</template>
</el-table-column>
- <el-table-column label="鏈�澶�" width="90" align="right">
+ <el-table-column :label="i18n('devicePingLog.column.max', '鏈�澶�')" width="90" align="right">
<template slot-scope="scope">
{{ formatLatency(scope.row.maxLatencyMs) }}
</template>
</el-table-column>
- <el-table-column prop="latestTimeLabel" label="鏇存柊鏃堕棿" width="170"></el-table-column>
- <el-table-column prop="message" label="璇存槑" min-width="200" show-overflow-tooltip></el-table-column>
- <el-table-column label="鎿嶄綔" width="110" align="center" fixed="right">
+ <el-table-column prop="latestTimeLabel" :label="i18n('devicePingLog.column.updateTime', '鏇存柊鏃堕棿')" width="170"></el-table-column>
+ <el-table-column prop="message" :label="i18n('devicePingLog.column.message', '璇存槑')" min-width="200" show-overflow-tooltip></el-table-column>
+ <el-table-column :label="i18n('devicePingLog.column.action', '鎿嶄綔')" width="110" align="center" fixed="right">
<template slot-scope="scope">
- <el-button size="mini" type="primary" plain @click="openDetail(scope.row)">鏌ョ湅璇︽儏</el-button>
+ <el-button size="mini" type="primary" plain @click="openDetail(scope.row)">{{ i18n('devicePingLog.viewDetail', '鏌ョ湅璇︽儏') }}</el-button>
</template>
</el-table-column>
</el-table>
@@ -383,37 +383,37 @@
<section class="panel">
<div class="panel-head">
- <div class="panel-title">璁惧璇︽儏</div>
+ <div class="panel-title">{{ i18n('devicePingLog.detailTitle', '璁惧璇︽儏') }}</div>
</div>
<div class="panel-body">
- <div v-if="!currentDevice" class="empty-shell">浠庝笂鏂硅澶囨�昏閫夋嫨涓�鍙拌澶囨煡鐪嬬绾ф槑缁�</div>
+ <div v-if="!currentDevice" class="empty-shell">{{ i18n('devicePingLog.detailEmpty', '浠庝笂鏂硅澶囨�昏閫夋嫨涓�鍙拌澶囨煡鐪嬬绾ф槑缁�') }}</div>
<div v-else class="detail-shell">
<div class="detail-toolbar">
<div class="detail-selected">{{ currentDevice.label }}</div>
<div class="toolbar-right">
<el-form :inline="true" size="small" :model="detailFilters" style="margin-bottom: -18px;">
- <el-form-item label="鏃堕棿鑼冨洿">
+ <el-form-item :label="i18n('devicePingLog.filter.timeRange', '鏃堕棿鑼冨洿')">
<el-date-picker
v-model="detailFilters.range"
type="datetimerange"
unlink-panels
- range-separator="鑷�"
- start-placeholder="寮�濮�"
- end-placeholder="缁撴潫"
+ :range-separator="i18n('devicePingLog.filter.rangeSeparator', '鑷�')"
+ :start-placeholder="i18n('devicePingLog.filter.start', '寮�濮�')"
+ :end-placeholder="i18n('devicePingLog.filter.end', '缁撴潫')"
style="width: 360px;">
</el-date-picker>
</el-form-item>
<el-form-item>
- <el-button @click="setQuickRange(30)">30 鍒嗛挓</el-button>
+ <el-button @click="setQuickRange(30)">{{ i18n('devicePingLog.quickRange.30m', '30 鍒嗛挓') }}</el-button>
</el-form-item>
<el-form-item>
- <el-button @click="setQuickRange(60)">1 灏忔椂</el-button>
+ <el-button @click="setQuickRange(60)">{{ i18n('devicePingLog.quickRange.1h', '1 灏忔椂') }}</el-button>
</el-form-item>
<el-form-item>
- <el-button @click="setQuickRange(360)">6 灏忔椂</el-button>
+ <el-button @click="setQuickRange(360)">{{ i18n('devicePingLog.quickRange.6h', '6 灏忔椂') }}</el-button>
</el-form-item>
<el-form-item>
- <el-button type="primary" :loading="detailLoading" @click="queryTrend">鏌ヨ</el-button>
+ <el-button type="primary" :loading="detailLoading" @click="queryTrend">{{ i18n('devicePingLog.query', '鏌ヨ') }}</el-button>
</el-form-item>
</el-form>
</div>
@@ -421,49 +421,49 @@
<div class="detail-summary-grid">
<div class="detail-card">
- <div class="detail-card-label">鐘舵��</div>
+ <div class="detail-card-label">{{ i18n('devicePingLog.detail.status', '鐘舵��') }}</div>
<div class="detail-card-value">{{ detailSummary.latestStatus || '--' }}</div>
</div>
<div class="detail-card">
- <div class="detail-card-label">鍖呭ぇ灏�</div>
+ <div class="detail-card-label">{{ i18n('devicePingLog.detail.packetSize', '鍖呭ぇ灏�') }}</div>
<div class="detail-card-value">{{ formatPacketSize(detailSummary.packetSize) }}</div>
</div>
<div class="detail-card">
- <div class="detail-card-label">鎴愬姛鐜�</div>
+ <div class="detail-card-label">{{ i18n('devicePingLog.detail.successRate', '鎴愬姛鐜�') }}</div>
<div class="detail-card-value">{{ formatPercent(detailSummary.successRate) }}</div>
</div>
<div class="detail-card">
- <div class="detail-card-label">骞冲潎</div>
+ <div class="detail-card-label">{{ i18n('devicePingLog.detail.avg', '骞冲潎') }}</div>
<div class="detail-card-value">{{ formatLatency(detailSummary.avgLatencyMs) }}</div>
</div>
<div class="detail-card">
- <div class="detail-card-label">鏈�灏�</div>
+ <div class="detail-card-label">{{ i18n('devicePingLog.detail.min', '鏈�灏�') }}</div>
<div class="detail-card-value">{{ formatLatency(detailSummary.minLatencyMs) }}</div>
</div>
<div class="detail-card">
- <div class="detail-card-label">鏈�澶�</div>
+ <div class="detail-card-label">{{ i18n('devicePingLog.detail.max', '鏈�澶�') }}</div>
<div class="detail-card-value">{{ formatLatency(detailSummary.maxLatencyMs) }}</div>
</div>
</div>
<div class="chart-grid">
<div class="chart-card">
- <div class="chart-title">寤惰繜</div>
- <div v-if="!series.length && !detailLoading" class="empty-shell">褰撳墠鑼冨洿鏆傛棤绉掔骇鏍锋湰</div>
+ <div class="chart-title">{{ i18n('devicePingLog.chart.latencyTitle', '寤惰繜') }}</div>
+ <div v-if="!series.length && !detailLoading" class="empty-shell">{{ i18n('devicePingLog.chart.empty', '褰撳墠鑼冨洿鏆傛棤绉掔骇鏍锋湰') }}</div>
<div v-show="series.length || detailLoading" ref="latencyChart" class="chart-box"></div>
</div>
<div class="chart-card">
- <div class="chart-title">鎴愬姛鐜� / 澶辫触娆℃暟</div>
- <div v-if="!series.length && !detailLoading" class="empty-shell">褰撳墠鑼冨洿鏆傛棤绉掔骇鏍锋湰</div>
+ <div class="chart-title">{{ i18n('devicePingLog.chart.availabilityTitle', '鎴愬姛鐜� / 澶辫触娆℃暟') }}</div>
+ <div v-if="!series.length && !detailLoading" class="empty-shell">{{ i18n('devicePingLog.chart.empty', '褰撳墠鑼冨洿鏆傛棤绉掔骇鏍锋湰') }}</div>
<div v-show="series.length || detailLoading" ref="availabilityChart" class="chart-box"></div>
</div>
</div>
<el-table :data="alerts" border stripe size="mini" style="width: 100%;">
- <el-table-column prop="timeLabel" label="鏃堕棿" width="180"></el-table-column>
- <el-table-column prop="status" label="鐘舵��" width="110"></el-table-column>
+ <el-table-column prop="timeLabel" :label="i18n('devicePingLog.alertColumn.time', '鏃堕棿')" width="180"></el-table-column>
+ <el-table-column prop="status" :label="i18n('devicePingLog.alertColumn.status', '鐘舵��')" width="110"></el-table-column>
<el-table-column prop="ip" label="IP" width="150"></el-table-column>
- <el-table-column prop="message" label="璇存槑" min-width="240" show-overflow-tooltip></el-table-column>
+ <el-table-column prop="message" :label="i18n('devicePingLog.alertColumn.message', '璇存槑')" min-width="240" show-overflow-tooltip></el-table-column>
</el-table>
</div>
</div>
@@ -475,6 +475,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/devicePingLog/devicePingLog.js?v=20260316_device_ping_packet_size_detail" charset="utf-8"></script>
+<script type="text/javascript" src="../../static/js/devicePingLog/devicePingLog.js?v=20260317_device_ping_i18n" charset="utf-8"></script>
</body>
</html>
--
Gitblit v1.9.1