style(frontend): refine badges and cycle stats

This commit is contained in:
elky
2026-07-18 00:05:22 +08:00
parent 88a057b8d9
commit e558f55cd9
8 changed files with 53 additions and 19 deletions
@@ -0,0 +1,31 @@
import { afterEach, describe, expect, it } from 'vitest'
import { createApp, h, type App } from 'vue'
import Badge from '../badge.vue'
const mountedApps: Array<{ app: App, root: HTMLElement }> = []
afterEach(() => {
for (const { app, root } of mountedApps.splice(0)) {
app.unmount()
root.remove()
}
})
describe('Badge', () => {
it('renders the transparent outline variant without the card background', () => {
const root = document.createElement('div')
document.body.appendChild(root)
const app = createApp({
render: () => h(Badge, { variant: 'outline-transparent' }, () => 'Fast'),
})
app.mount(root)
mountedApps.push({ app, root })
const badge = root.firstElementChild
expect(badge?.classList.contains('border-border')).toBe(true)
expect(badge?.classList.contains('bg-transparent')).toBe(true)
expect(badge?.classList.contains('bg-card/50')).toBe(false)
})
})
+2 -1
View File
@@ -20,6 +20,7 @@ const badgeVariants = cva(
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground border-border bg-card/50',
'outline-transparent': 'text-foreground border-border bg-transparent',
success:
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
warning:
@@ -35,7 +36,7 @@ const badgeVariants = cva(
)
interface Props {
variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning' | 'dark'
variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'outline-transparent' | 'success' | 'warning' | 'dark'
class?: string
}
@@ -7,15 +7,13 @@
<div
v-for="row in cycleMetricRows"
:key="`${row.key}-${variant}-cycle-row`"
class="flex items-center justify-between gap-2"
class="truncate"
:title="`${row.label} ${row.valueText}`"
>
<span class="shrink-0 text-muted-foreground">{{ row.label }}</span>
<span
class="min-w-0 truncate text-right font-medium tabular-nums text-foreground/90"
:data-testid="variant === 'desktop' ? `pool-stats-cycle-${row.key}` : undefined"
:title="row.valueText"
>
{{ row.valueText }}
{{ row.label }} {{ row.valueText }}
</span>
</div>
</div>
@@ -116,7 +114,7 @@ const cycleMetricRows = computed(() => {
})
const cycleContainerClass = computed(() => [
'mx-auto w-[132px] space-y-1 text-[9px] leading-3',
'mx-auto w-[132px] space-y-0.5 text-[10px] leading-4 text-foreground/90 tabular-nums',
props.variant === 'mobile' ? 'py-0.5' : '',
].filter(Boolean).join(' '))
@@ -30,7 +30,7 @@ describe('pool key display panels', () => {
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')).toBeTruthy()
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')?.className).toContain('w-[132px]')
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('12/88')
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 12/88')
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-large-base"]')).toBeNull()
expect(root.textContent).not.toContain('5H')
@@ -98,7 +98,7 @@ describe('pool key display panels', () => {
app.use(createI18n())
app.mount(root)
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('31')
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 31')
expect(root.querySelector('[data-testid="pool-stats-cycle-single-marker"]')).toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-bar-request_count"]')).toBeNull()
expect(root.textContent).not.toContain('月')
@@ -30,7 +30,7 @@
:key="badge.key"
:data-usage-model-badge="badge.key"
:data-request-detail-model-badge="context === 'detail' ? badge.key : undefined"
variant="outline"
:variant="badge.variant"
class="h-4 shrink-0 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4"
:class="badge.className"
:title="badge.title"
@@ -52,7 +52,7 @@
:key="badge.key"
:data-usage-model-badge="badge.key"
:data-request-detail-model-badge="context === 'detail' ? badge.key : undefined"
variant="outline"
:variant="badge.variant"
class="h-4 shrink-0 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4"
:class="badge.className"
:title="badge.title"
@@ -76,6 +76,7 @@ type ModelBadgeKey = 'reasoning' | 'fast' | 'cyber'
interface ModelBadgePresentation {
key: ModelBadgeKey
label: string
variant: 'outline' | 'outline-transparent'
className: string
title: string
ariaLabel: string
@@ -130,6 +131,7 @@ const modelBadges = computed<ModelBadgePresentation[]>(() => {
badges.push({
key: 'reasoning',
label: reasoningLabel.value,
variant: 'outline',
className: 'border-primary/30 bg-primary/5 text-primary',
title: `Reasoning: ${reasoningLabel.value}`,
ariaLabel: `Reasoning: ${reasoningLabel.value}`,
@@ -140,7 +142,8 @@ const modelBadges = computed<ModelBadgePresentation[]>(() => {
badges.push({
key: 'fast',
label: 'Fast',
className: 'border-amber-400/50 !bg-transparent text-amber-700 dark:border-amber-300/40 dark:text-amber-300',
variant: 'outline-transparent',
className: 'text-amber-700 dark:text-amber-300',
title: '上游请求档位:Fast\n计费档位:Fast',
ariaLabel: '上游请求档位:Fast,计费档位:Fast',
})
@@ -150,6 +153,7 @@ const modelBadges = computed<ModelBadgePresentation[]>(() => {
badges.push({
key: 'cyber',
label: 'Cyber',
variant: 'outline',
className: 'border-primary/30 bg-primary/5 text-rose-600 dark:text-rose-300',
title: '上游 Cyber Policy 拒绝',
ariaLabel: '上游 Cyber Policy 拒绝',
@@ -1579,7 +1579,7 @@ function buildServiceTierBadgePresentation(
const title = titleLines.join('\n')
return {
label: 'Fast',
className: 'border-amber-400/50 !bg-transparent text-amber-700 dark:border-amber-300/40 dark:text-amber-300',
className: '!bg-transparent text-amber-700 dark:text-amber-300',
title,
ariaLabel: titleLines.join(''),
}
@@ -339,9 +339,9 @@ describe('UsageRecordsTable', () => {
expect(reasoningBadge?.classList.contains('border-primary/30')).toBe(true)
expect(reasoningBadge?.classList.contains('bg-primary/5')).toBe(true)
expect(reasoningBadge?.classList.contains('text-primary')).toBe(true)
expect(fastBadge?.classList.contains('border-amber-400/50')).toBe(true)
expect(fastBadge?.classList.contains('!bg-transparent')).toBe(true)
expect(fastBadge?.classList.contains('bg-transparent')).toBe(false)
expect(fastBadge?.getAttribute('variant')).toBe('outline-transparent')
expect(fastBadge?.classList.contains('border-amber-400/50')).toBe(false)
expect(fastBadge?.classList.contains('!bg-transparent')).toBe(false)
expect(fastBadge?.classList.contains('bg-amber-400/10')).toBe(false)
expect(fastBadge?.classList.contains('text-amber-700')).toBe(true)
expect(cyberBadge?.classList.contains('border-primary/30')).toBe(true)
@@ -577,8 +577,8 @@ describe('PoolManagement Codex cycle stats mode', () => {
expect(root.querySelector('[data-testid="pool-stats-mode-control"]')).toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')).not.toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('7/12')
expect(root.querySelector('[data-testid="pool-stats-cycle-total_tokens"]')?.textContent?.trim()).toBe('2.5K/5K')
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 7/12')
expect(root.querySelector('[data-testid="pool-stats-cycle-total_tokens"]')?.textContent?.trim()).toBe('Token 2.5K/5K')
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-large-base"]')).toBeNull()
expect(endpointMocks.listPoolKeys).toHaveBeenLastCalledWith(
@@ -707,7 +707,7 @@ describe('PoolManagement Codex cycle stats mode', () => {
expect(periodLabels).toContain('月')
expect(periodLabels).not.toContain('5H')
expect(periodLabels).not.toContain('周')
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('23')
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 23')
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-bar-request_count"]')).toBeNull()
expect(root.querySelector('[data-testid="pool-stats-cycle-single-marker"]')).toBeNull()