From 8640a62319e1764a9ec29465f568ca203c698620 Mon Sep 17 00:00:00 2001 From: Ethan0x0000 <3352979663@qq.com> Date: Mon, 16 Mar 2026 16:22:51 +0800 Subject: [PATCH] refactor: extract formatCompactNumber util and add last_used_at to refresh key - Add formatCompactNumber() for consistent large-number formatting (K/M/B) - Include last_used_at in OpenAI usage refresh key for better change detection - Add .gitattributes eol=lf rules for frontend source files --- .gitattributes | 7 ++++++ .../__tests__/accountUsageRefresh.spec.ts | 24 +++++++++++++++++++ .../__tests__/formatCompactNumber.spec.ts | 22 +++++++++++++++++ frontend/src/utils/accountUsageRefresh.ts | 3 ++- frontend/src/utils/format.ts | 20 ++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 frontend/src/utils/__tests__/formatCompactNumber.spec.ts diff --git a/.gitattributes b/.gitattributes index 3db3b83d..37e3bee2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,6 +4,13 @@ backend/migrations/*.sql text eol=lf # Go 源代码文件 *.go text eol=lf +# 前端 源代码文件 +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.vue text eol=lf + # Shell 脚本 *.sh text eol=lf diff --git a/frontend/src/utils/__tests__/accountUsageRefresh.spec.ts b/frontend/src/utils/__tests__/accountUsageRefresh.spec.ts index ae13d690..aef73b0f 100644 --- a/frontend/src/utils/__tests__/accountUsageRefresh.spec.ts +++ b/frontend/src/utils/__tests__/accountUsageRefresh.spec.ts @@ -8,6 +8,7 @@ describe('buildOpenAIUsageRefreshKey', () => { platform: 'openai', type: 'oauth', updated_at: '2026-03-07T10:00:00Z', + last_used_at: '2026-03-07T09:59:00Z', extra: { codex_usage_updated_at: '2026-03-07T10:00:00Z', codex_5h_used_percent: 0, @@ -27,12 +28,35 @@ describe('buildOpenAIUsageRefreshKey', () => { expect(buildOpenAIUsageRefreshKey(base)).not.toBe(buildOpenAIUsageRefreshKey(next)) }) + it('会在 last_used_at 变化时生成不同 key', () => { + const base = { + id: 3, + platform: 'openai', + type: 'oauth', + updated_at: '2026-03-07T10:00:00Z', + last_used_at: '2026-03-07T10:00:00Z', + extra: { + codex_usage_updated_at: '2026-03-07T10:00:00Z', + codex_5h_used_percent: 12, + codex_7d_used_percent: 24 + } + } as any + + const next = { + ...base, + last_used_at: '2026-03-07T10:02:00Z' + } + + expect(buildOpenAIUsageRefreshKey(base)).not.toBe(buildOpenAIUsageRefreshKey(next)) + }) + it('非 OpenAI OAuth 账号返回空 key', () => { expect(buildOpenAIUsageRefreshKey({ id: 2, platform: 'anthropic', type: 'oauth', updated_at: '2026-03-07T10:00:00Z', + last_used_at: '2026-03-07T10:00:00Z', extra: {} } as any)).toBe('') }) diff --git a/frontend/src/utils/__tests__/formatCompactNumber.spec.ts b/frontend/src/utils/__tests__/formatCompactNumber.spec.ts new file mode 100644 index 00000000..a5a9ed9f --- /dev/null +++ b/frontend/src/utils/__tests__/formatCompactNumber.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' +import { formatCompactNumber } from '../format' + +describe('formatCompactNumber', () => { + it('formats boundary values with K/M/B', () => { + expect(formatCompactNumber(0)).toBe('0') + expect(formatCompactNumber(999)).toBe('999') + expect(formatCompactNumber(1000)).toBe('1.0K') + expect(formatCompactNumber(999999)).toBe('1000.0K') + expect(formatCompactNumber(1000000)).toBe('1.0M') + expect(formatCompactNumber(1000000000)).toBe('1.0B') + }) + + it('supports disabling billion unit (requests style)', () => { + expect(formatCompactNumber(1000000000, { allowBillions: false })).toBe('1000.0M') + }) + + it('returns 0 for nullish input', () => { + expect(formatCompactNumber(null)).toBe('0') + expect(formatCompactNumber(undefined)).toBe('0') + }) +}) diff --git a/frontend/src/utils/accountUsageRefresh.ts b/frontend/src/utils/accountUsageRefresh.ts index 219ac57f..3406c7a5 100644 --- a/frontend/src/utils/accountUsageRefresh.ts +++ b/frontend/src/utils/accountUsageRefresh.ts @@ -5,7 +5,7 @@ const normalizeUsageRefreshValue = (value: unknown): string => { return String(value) } -export const buildOpenAIUsageRefreshKey = (account: Pick): string => { +export const buildOpenAIUsageRefreshKey = (account: Pick): string => { if (account.platform !== 'openai' || account.type !== 'oauth') { return '' } @@ -14,6 +14,7 @@ export const buildOpenAIUsageRefreshKey = (account: Pick= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(1)}B` + if (abs >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M` + if (abs >= 1_000) return `${(num / 1_000).toFixed(1)}K` + return num.toString() +} + /** * 格式化倒计时(从现在到目标时间的剩余时间) * @param targetDate 目标日期字符串或 Date 对象