Merge pull request #5837 from xuhaihan/fix/cn-provider-quota-cell-layout

fix(frontend): prevent CN quota labels overlapping bars
This commit is contained in:
Wesley Liddick
2026-08-19 21:05:26 +08:00
committed by GitHub
2 changed files with 104 additions and 6 deletions
@@ -1,10 +1,14 @@
<template>
<div v-if="visible" class="space-y-1">
<div
v-if="visible"
data-test="cn-provider-quota"
class="min-w-[220px] space-y-1"
>
<div class="flex flex-wrap items-center gap-1.5">
<button
type="button"
:class="[
'inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 text-[10px] font-medium transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-dark-600',
'inline-flex items-center gap-0.5 whitespace-nowrap rounded px-1.5 py-0.5 text-[10px] font-medium leading-4 transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-dark-600',
platformTextClass(account.platform)
]"
:disabled="loading"
@@ -31,8 +35,18 @@
<!-- Tier rows: 5h + weekly utilization bars -->
<div v-if="data?.success && data.tiers?.length" class="space-y-1">
<div v-for="tier in data.tiers" :key="tier.window" class="flex items-center gap-1.5 text-[10px]">
<span class="w-10 shrink-0 text-gray-500 dark:text-gray-400">{{ windowLabel(tier.window) }}</span>
<div
v-for="tier in data.tiers"
:key="tier.window"
data-test="cn-provider-quota-tier"
class="flex min-w-0 items-center gap-1.5 text-[10px] leading-4"
>
<span
data-test="cn-provider-quota-label"
class="w-14 shrink-0 whitespace-nowrap text-gray-500 dark:text-gray-400"
>
{{ windowLabel(tier.window) }}
</span>
<div class="h-1.5 w-16 shrink-0 overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
class="h-full rounded-full transition-all"
@@ -43,13 +57,21 @@
<span :class="['shrink-0 font-medium', utilizationTextColor(tier.used_percent)]">
{{ Math.round(tier.used_percent) }}%
</span>
<span v-if="tier.reset_at" class="truncate text-gray-400 dark:text-gray-500" :title="tier.reset_at">
<span
v-if="tier.reset_at"
class="min-w-0 truncate text-gray-400 dark:text-gray-500"
:title="tier.reset_at"
>
· {{ formatReset(tier.reset_at) }}
</span>
</div>
</div>
<div v-if="error" class="truncate text-[10px] text-red-600 dark:text-red-400" :title="error">
<div
v-if="error"
class="truncate text-[10px] leading-4 text-red-600 dark:text-red-400"
:title="error"
>
{{ truncatedError }}
</div>
</div>
@@ -0,0 +1,76 @@
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import CNProviderQuotaCell from '../CNProviderQuotaCell.vue'
import type { Account } from '@/types'
const { queryQuota } = vi.hoisted(() => ({
queryQuota: vi.fn()
}))
vi.mock('@/api/admin', () => ({
adminAPI: {
cnProviders: { queryQuota }
}
}))
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key
})
}))
const account = {
id: 7,
platform: 'zhipu',
type: 'apikey',
credentials: { account_mode: 'coding' },
extra: {
zhipu_5h_used_percent: 0,
zhipu_weekly_used_percent: 27,
zhipu_5h_reset_at: '2026-08-18T12:30:00+08:00',
zhipu_weekly_reset_at: '2026-08-22T00:00:00+08:00',
zhipu_usage_updated_at: new Date().toISOString()
}
} as Account
describe('CNProviderQuotaCell', () => {
beforeEach(() => {
queryQuota.mockReset()
})
it('keeps the compact quota stack readable inside the account table cell', async () => {
queryQuota.mockResolvedValue({
success: true,
tiers: [
{ window: '5h', used_percent: 0, reset_at: '2026-08-18T12:30:00+08:00' },
{ window: 'weekly', used_percent: 27, reset_at: '2026-08-22T00:00:00+08:00' }
]
})
const wrapper = mount(CNProviderQuotaCell, { props: { account } })
const root = wrapper.get('[data-test="cn-provider-quota"]')
expect(root.classes()).toContain('min-w-[220px]')
const probeButton = root.get('button')
expect(probeButton.classes()).toContain('whitespace-nowrap')
expect(probeButton.classes()).toContain('leading-4')
await probeButton.trigger('click')
await flushPromises()
const tiers = root.findAll('[data-test="cn-provider-quota-tier"]')
expect(tiers).toHaveLength(2)
for (const tier of tiers) {
expect(tier.classes()).toContain('min-w-0')
expect(tier.classes()).toContain('leading-4')
}
const labels = root.findAll('[data-test="cn-provider-quota-label"]')
expect(labels).toHaveLength(2)
for (const label of labels) {
expect(label.classes()).toContain('w-14')
expect(label.classes()).toContain('whitespace-nowrap')
}
expect(queryQuota).toHaveBeenCalledWith(account.id)
})
})