mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 15:57:53 +08:00
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:
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user