diff --git a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue index 8e5c9e8379..16e86b2161 100644 --- a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue +++ b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue @@ -10,6 +10,7 @@ import { opsAPI, type OpsDashboardOverview, type OpsMetricThresholds, type OpsRe import type { OpsRequestDetailsPreset } from './OpsRequestDetailsModal.vue' import { useAdminSettingsStore } from '@/stores' import { formatNumber } from '@/utils/format' +import { formatMemorySizeMB } from '../utils/opsFormatters' type RealtimeWindow = '1min' | '5min' | '30min' | '1h' @@ -1462,7 +1463,7 @@ function handleToolbarRefresh() { {{ systemMetrics?.memory_used_mb == null || systemMetrics?.memory_total_mb == null ? '-' - : `${formatNumber(systemMetrics.memory_used_mb)} / ${formatNumber(systemMetrics.memory_total_mb)} MB` + : `${formatMemorySizeMB(systemMetrics.memory_used_mb)} / ${formatMemorySizeMB(systemMetrics.memory_total_mb)}` }} diff --git a/frontend/src/views/admin/ops/utils/__tests__/opsFormatters.spec.ts b/frontend/src/views/admin/ops/utils/__tests__/opsFormatters.spec.ts new file mode 100644 index 0000000000..56b121b37f --- /dev/null +++ b/frontend/src/views/admin/ops/utils/__tests__/opsFormatters.spec.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' + +import { formatMemorySizeMB } from '../opsFormatters' + +describe('formatMemorySizeMB', () => { + it('keeps capacities below one GiB in MB', () => { + expect(formatMemorySizeMB(61)).toBe('61 MB') + expect(formatMemorySizeMB(1023)).toBe('1023 MB') + }) + + it('converts capacities at or above one GiB to GB', () => { + expect(formatMemorySizeMB(1024)).toBe('1 GB') + expect(formatMemorySizeMB(1536)).toBe('1.5 GB') + expect(formatMemorySizeMB(32768)).toBe('32 GB') + }) + + it('handles zero and invalid values without producing a misleading unit', () => { + expect(formatMemorySizeMB(0)).toBe('0 MB') + expect(formatMemorySizeMB(-1)).toBe('-') + expect(formatMemorySizeMB(Number.NaN)).toBe('-') + expect(formatMemorySizeMB(Number.POSITIVE_INFINITY)).toBe('-') + expect(formatMemorySizeMB(null)).toBe('-') + expect(formatMemorySizeMB(undefined)).toBe('-') + }) +}) diff --git a/frontend/src/views/admin/ops/utils/opsFormatters.ts b/frontend/src/views/admin/ops/utils/opsFormatters.ts index d503b5a5f1..9c96b0b021 100644 --- a/frontend/src/views/admin/ops/utils/opsFormatters.ts +++ b/frontend/src/views/admin/ops/utils/opsFormatters.ts @@ -73,3 +73,16 @@ export function formatByteRate(bytes: number, windowMinutes: number): string { const seconds = Math.max(1, (windowMinutes || 1) * 60) return `${formatBytes(bytes / seconds, 1)}/s` } + +/** + * 格式化 Ops 接口返回的内存容量。 + * + * 后端的 `*_mb` 字段以 1024² 字节为单位换算得到;这里沿用该口径, + * 避免把大容量数值交给通用数字缩写格式化后出现“3.2万 MB”这类不直观的显示。 + */ +export function formatMemorySizeMB(value: number | null | undefined): string { + if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) return '-' + if (value === 0) return '0 MB' + + return formatBytes(value * 1024 * 1024, 1) +}