diff --git a/api/admin/v1/channel.go b/api/admin/v1/channel.go index 96af813..3a3abb5 100644 --- a/api/admin/v1/channel.go +++ b/api/admin/v1/channel.go @@ -243,12 +243,12 @@ type ChannelHealthTrendRes struct { Points []HealthTrendPoint `json:"points"` } -// HealthTrendPoint 健康趋势数据点 +// HealthTrendPoint 健康趋势数据点(健康度、延迟取整展示,不含小数) type HealthTrendPoint struct { SnapshotAt string `json:"snapshot_at"` - HealthScore float64 `json:"health_score"` + HealthScore int64 `json:"health_score"` SuccessRate float64 `json:"success_rate"` - LatencyMs float64 `json:"latency_ms"` + LatencyMs int64 `json:"latency_ms"` StabilityScore float64 `json:"stability_score"` ConsecutiveFailures int `json:"consecutive_failures"` } diff --git a/internal/logic/admin/channel.go b/internal/logic/admin/channel.go index e16fbe6..989deea 100644 --- a/internal/logic/admin/channel.go +++ b/internal/logic/admin/channel.go @@ -772,11 +772,11 @@ func defaultProviderURL(t int) string { return defaultProviderURLs[t] } -// GetChannelHealthTrend 获取渠道健康趋势数据 +// GetChannelHealthTrend 获取渠道健康趋势数据(健康度、延迟在 SQL 层四舍五入取整,避免展示小数) func (s *sAdmin) GetChannelHealthTrend(ctx context.Context, req *v1.ChannelHealthTrendReq) (*v1.ChannelHealthTrendRes, error) { var points []v1.HealthTrendPoint err := dao.ChnHealthSnapshots.Ctx(ctx). - Fields("snapshot_at, health_score, success_rate, latency_ms, stability_score, consecutive_failures"). + Fields("snapshot_at, ROUND(health_score)::int AS health_score, success_rate, ROUND(latency_ms)::int AS latency_ms, stability_score, consecutive_failures"). Where("channel_id", req.ID). Where("snapshot_at >= ?", gtime.Now().Add(-time.Duration(req.Hours)*time.Hour)). OrderAsc("snapshot_at"). diff --git a/web/admin/src/views/ai/ChannelDetailPage.vue b/web/admin/src/views/ai/ChannelDetailPage.vue index b6f5e98..7da29f7 100644 --- a/web/admin/src/views/ai/ChannelDetailPage.vue +++ b/web/admin/src/views/ai/ChannelDetailPage.vue @@ -399,10 +399,11 @@ function renderTrendChart() { return } const times = trendData.value.map((p: any) => p.snapshot_at) - const scores = trendData.value.map((p: any) => p.health_score) - const latencies = trendData.value.map((p: any) => p.latency_ms) + const scores = trendData.value.map((p: any) => Math.round(Number(p.health_score) || 0)) + const latencies = trendData.value.map((p: any) => Math.round(Number(p.latency_ms) || 0)) trendChart.setOption({ - tooltip: { trigger: 'axis' }, + // 健康度、延迟统一取整展示 + tooltip: { trigger: 'axis', valueFormatter: (value: any) => String(Math.round(Number(value) || 0)) }, legend: { data: ['健康度', '延迟(ms)'], bottom: 0 }, grid: { left: 50, right: 50, top: 30, bottom: 45 }, xAxis: { type: 'category', data: times, axisLabel: { fontSize: 11 } }, @@ -412,7 +413,7 @@ function renderTrendChart() { ], series: [ { - name: '健康度', type: 'line', data: scores, smooth: true, lineStyle: { width: 2 }, + name: '健康度', type: 'line', data: scores, smooth: true, showSymbol: false, lineStyle: { width: 2 }, itemStyle: { color: '#10b981' }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(16,185,129,0.3)' }, diff --git a/web/admin/src/views/monitor/RealtimeMonitorPage.vue b/web/admin/src/views/monitor/RealtimeMonitorPage.vue index 81af7b6..a699dc4 100644 --- a/web/admin/src/views/monitor/RealtimeMonitorPage.vue +++ b/web/admin/src/views/monitor/RealtimeMonitorPage.vue @@ -55,9 +55,9 @@ function updateCharts() { }, yAxis: { type: 'value', minInterval: 1, splitLine: { lineStyle: { type: 'dashed' } } }, series: [ - { name: '总数', type: 'line', data: history.map((h: any) => h.total_active), smooth: true, lineStyle: { width: 2 }, itemStyle: { color: '#165DFF' }, areaStyle: { color: 'rgba(22,93,255,0.06)' } }, - { name: '流式', type: 'line', data: history.map((h: any) => h.streaming_active), smooth: true, lineStyle: { width: 1.5 }, itemStyle: { color: '#00B42A' } }, - { name: '非流式', type: 'line', data: history.map((h: any) => h.non_streaming_active), smooth: true, lineStyle: { width: 1.5 }, itemStyle: { color: '#FF7D00' } }, + { name: '总数', type: 'line', data: history.map((h: any) => h.total_active), smooth: true, showSymbol: false, lineStyle: { width: 2 }, itemStyle: { color: '#165DFF' }, areaStyle: { color: 'rgba(22,93,255,0.06)' } }, + { name: '流式', type: 'line', data: history.map((h: any) => h.streaming_active), smooth: true, showSymbol: false, lineStyle: { width: 1.5 }, itemStyle: { color: '#00B42A' } }, + { name: '非流式', type: 'line', data: history.map((h: any) => h.non_streaming_active), smooth: true, showSymbol: false, lineStyle: { width: 1.5 }, itemStyle: { color: '#FF7D00' } }, ], } @@ -80,8 +80,8 @@ function updateCharts() { splitLine: { lineStyle: { type: 'dashed' } }, }, series: [ - { name: '入站', type: 'line', areaStyle: { color: 'rgba(22,93,255,0.08)' }, data: bwHistory.map((h: any) => h.bytes_in_per_sec), smooth: true, lineStyle: { width: 2 }, itemStyle: { color: '#165DFF' } }, - { name: '出站', type: 'line', areaStyle: { color: 'rgba(0,180,42,0.08)' }, data: bwHistory.map((h: any) => h.bytes_out_per_sec), smooth: true, lineStyle: { width: 2 }, itemStyle: { color: '#00B42A' } }, + { name: '入站', type: 'line', areaStyle: { color: 'rgba(22,93,255,0.08)' }, data: bwHistory.map((h: any) => h.bytes_in_per_sec), smooth: true, showSymbol: false, lineStyle: { width: 2 }, itemStyle: { color: '#165DFF' } }, + { name: '出站', type: 'line', areaStyle: { color: 'rgba(0,180,42,0.08)' }, data: bwHistory.map((h: any) => h.bytes_out_per_sec), smooth: true, showSymbol: false, lineStyle: { width: 2 }, itemStyle: { color: '#00B42A' } }, ], }