mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
fix(frontend): unify usage window rendering and tighten alert thresholds
CN provider accounts (Kimi/Zhipu/DeepSeek) and the monitor quota view each
hand-rolled their own usage bar, so the same account column rendered three
different label styles, bar widths and threshold palettes. Both now reuse
UsageProgressBar, the component the other five platforms already share.
Also move the default utilization thresholds from >=100 red / >=80 amber to
>=90 / >=75. Turning red only once the quota is exhausted is an after-the-fact
notice rather than a warning. The remainingCapacity mode keeps its 20/50 pair,
which is already a warning semantic.
- UsageProgressBar: add labelWidth 'fixed' | 'auto' so the monitor view can
render composite labels ("Pro/7d") without breaking column alignment; the
fixed branch is byte-identical, so the existing platforms are untouched
- drop the superseded utilizationColor/utilizationTextColor/formatReset copies
- shorten CN window labels to 5h / 7d and remove the now-dead resetSoon keys
This commit is contained in:
@@ -4,38 +4,18 @@
|
||||
data-test="cn-provider-quota"
|
||||
class="min-w-[220px] space-y-1"
|
||||
>
|
||||
<!-- Tier rows: 5h + weekly utilization bars (snapshot renders on mount) -->
|
||||
<!-- Tier rows: 5h + weekly utilization bars (snapshot renders on mount).
|
||||
复用账号页 UsageProgressBar:同阈值配色、同倒计时格式。 -->
|
||||
<div v-if="data?.success && data.tiers?.length" class="space-y-1">
|
||||
<div
|
||||
<UsageProgressBar
|
||||
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"
|
||||
:class="utilizationColor(tier.used_percent)"
|
||||
:style="{ width: `${Math.min(100, Math.max(0, tier.used_percent))}%` }"
|
||||
/>
|
||||
</div>
|
||||
<span :class="['shrink-0 font-medium', utilizationTextColor(tier.used_percent)]">
|
||||
{{ Math.round(tier.used_percent) }}%
|
||||
</span>
|
||||
<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>
|
||||
:label="windowLabel(tier.window)"
|
||||
:color="tier.window === 'weekly' ? 'emerald' : 'indigo'"
|
||||
:utilization="tier.used_percent"
|
||||
:resets-at="tier.reset_at"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Explicit refresh action (aligned with the OpenAI "Query" / Grok "Probe"
|
||||
@@ -86,6 +66,7 @@ import { adminAPI } from '@/api/admin'
|
||||
import type { CNProviderQuotaProbeResult } from '@/api/admin/cnProviders'
|
||||
import type { Account } from '@/types'
|
||||
import { cnQuotaCellVisible } from './credentialsBuilder'
|
||||
import UsageProgressBar from './UsageProgressBar.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
account: Account
|
||||
@@ -185,33 +166,6 @@ const windowLabel = (window: string) =>
|
||||
? t('admin.accounts.cnProviders.windowWeekly')
|
||||
: t('admin.accounts.cnProviders.window5h')
|
||||
|
||||
const utilizationColor = (pct: number) => {
|
||||
if (pct >= 90) return 'bg-red-500'
|
||||
if (pct >= 75) return 'bg-amber-500'
|
||||
return 'bg-emerald-500'
|
||||
}
|
||||
|
||||
const utilizationTextColor = (pct: number) => {
|
||||
if (pct >= 90) return 'text-red-600 dark:text-red-400'
|
||||
if (pct >= 75) return 'text-amber-600 dark:text-amber-400'
|
||||
return 'text-emerald-600 dark:text-emerald-400'
|
||||
}
|
||||
|
||||
// 重置时间相对/绝对简短显示。
|
||||
const formatReset = (iso: string) => {
|
||||
const d = new Date(iso)
|
||||
if (isNaN(d.getTime())) return iso
|
||||
const now = Date.now()
|
||||
const diffMs = d.getTime() - now
|
||||
if (diffMs <= 0) return t('admin.accounts.cnProviders.resetSoon')
|
||||
if (diffMs < 3_600_000) return `${Math.max(1, Math.round(diffMs / 60_000))}m`
|
||||
const hours = Math.round(diffMs / 3_600_000)
|
||||
if (hours < 48) return `${hours}h`
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(d.getDate()).padStart(2, '0')
|
||||
return `${mm}-${dd}`
|
||||
}
|
||||
|
||||
const handleProbe = async () => {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
@@ -27,10 +27,8 @@
|
||||
|
||||
<!-- Progress bar row -->
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- Label badge (fixed width for alignment) -->
|
||||
<span
|
||||
:class="['w-[32px] shrink-0 rounded px-1 text-center text-[10px] font-medium', labelClass]"
|
||||
>
|
||||
<!-- Label badge (label-width: fixed = 定宽居中, auto = 限宽截断左对齐) -->
|
||||
<span :class="[labelSizeClass, labelClass]">
|
||||
{{ label }}
|
||||
</span>
|
||||
|
||||
@@ -62,15 +60,20 @@ import { useI18n } from 'vue-i18n'
|
||||
import type { WindowStats } from '@/types'
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
utilization: number // Percentage (0-100+)
|
||||
resetsAt?: string | null
|
||||
color: 'indigo' | 'emerald' | 'purple' | 'amber'
|
||||
windowStats?: WindowStats | null
|
||||
showNowWhenIdle?: boolean
|
||||
remainingCapacity?: boolean
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label: string
|
||||
utilization: number // Percentage (0-100+)
|
||||
resetsAt?: string | null
|
||||
color: 'indigo' | 'emerald' | 'purple' | 'amber'
|
||||
windowStats?: WindowStats | null
|
||||
showNowWhenIdle?: boolean
|
||||
remainingCapacity?: boolean
|
||||
/** fixed: 定宽居中徽章(账号页纵向对齐);auto: 限宽截断左对齐(监控页组合标签) */
|
||||
labelWidth?: 'fixed' | 'auto'
|
||||
}>(),
|
||||
{ labelWidth: 'fixed' }
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -108,6 +111,14 @@ const labelClass = computed(() => {
|
||||
return colors[props.color]
|
||||
})
|
||||
|
||||
// Label badge width mode: fixed 定宽保证账号页纵向对齐;auto 限宽截断适配
|
||||
// 监控页「Pro/7 天」类组合标签。百分比列在两种模式下保持不变。
|
||||
const labelSizeClass = computed(() =>
|
||||
props.labelWidth === 'auto'
|
||||
? 'max-w-[72px] shrink-0 truncate rounded px-1 text-left text-[10px] font-medium'
|
||||
: 'w-[32px] shrink-0 rounded px-1 text-center text-[10px] font-medium'
|
||||
)
|
||||
|
||||
// Progress bar color based on utilization
|
||||
const barClass = computed(() => {
|
||||
if (props.remainingCapacity) {
|
||||
@@ -118,9 +129,9 @@ const barClass = computed(() => {
|
||||
}
|
||||
return 'bg-green-500'
|
||||
}
|
||||
if (props.utilization >= 100) {
|
||||
if (props.utilization >= 90) {
|
||||
return 'bg-red-500'
|
||||
} else if (props.utilization >= 80) {
|
||||
} else if (props.utilization >= 75) {
|
||||
return 'bg-amber-500'
|
||||
} else {
|
||||
return 'bg-green-500'
|
||||
@@ -137,9 +148,9 @@ const textClass = computed(() => {
|
||||
}
|
||||
return 'text-gray-600 dark:text-gray-400'
|
||||
}
|
||||
if (props.utilization >= 100) {
|
||||
if (props.utilization >= 90) {
|
||||
return 'text-red-600 dark:text-red-400'
|
||||
} else if (props.utilization >= 80) {
|
||||
} else if (props.utilization >= 75) {
|
||||
return 'text-amber-600 dark:text-amber-400'
|
||||
} else {
|
||||
return 'text-gray-600 dark:text-gray-400'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import CNProviderQuotaCell from '../CNProviderQuotaCell.vue'
|
||||
import UsageProgressBar from '../UsageProgressBar.vue'
|
||||
import type { Account } from '@/types'
|
||||
|
||||
const { queryQuota } = vi.hoisted(() => ({
|
||||
@@ -13,11 +14,17 @@ vi.mock('@/api/admin', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string) => key
|
||||
})
|
||||
}))
|
||||
// 保留 vue-i18n 真实导出:UsageProgressBar 依赖 @/utils/format → @/i18n,
|
||||
// 其模块级 createI18n 需要真实 createI18n 存在。
|
||||
vi.mock('vue-i18n', async () => {
|
||||
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({
|
||||
t: (key: string) => key
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const account = {
|
||||
id: 7,
|
||||
@@ -38,7 +45,7 @@ describe('CNProviderQuotaCell', () => {
|
||||
queryQuota.mockReset()
|
||||
})
|
||||
|
||||
it('keeps the compact quota stack readable inside the account table cell', async () => {
|
||||
it('renders tier rows through the shared UsageProgressBar inside the account table cell', async () => {
|
||||
queryQuota.mockResolvedValue({
|
||||
success: true,
|
||||
tiers: [
|
||||
@@ -51,27 +58,30 @@ describe('CNProviderQuotaCell', () => {
|
||||
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 flushPromises()
|
||||
expect(queryQuota).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('27%')
|
||||
|
||||
// probe 按钮文案是动词 key(i18n mock 返回 key 本身),点击触发查询
|
||||
const probeButton = root.get('[data-test="cn-provider-quota-probe"]')
|
||||
expect(probeButton.text()).toBe('admin.accounts.cnProviders.probe')
|
||||
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)
|
||||
|
||||
// tier 行由 UsageProgressBar 渲染:数量、label/color/utilization/reset 逐行对齐
|
||||
expect(root.findAll('[data-test="cn-provider-quota-tier"]')).toHaveLength(2)
|
||||
const bars = root.findAllComponents(UsageProgressBar)
|
||||
expect(bars).toHaveLength(2)
|
||||
expect(bars[0].props('label')).toBe('admin.accounts.cnProviders.window5h')
|
||||
expect(bars[0].props('utilization')).toBe(0)
|
||||
expect(bars[0].props('color')).toBe('indigo')
|
||||
expect(bars[0].props('resetsAt')).toBe('2026-08-18T12:30:00+08:00')
|
||||
expect(bars[1].props('label')).toBe('admin.accounts.cnProviders.windowWeekly')
|
||||
expect(bars[1].props('utilization')).toBe(27)
|
||||
expect(bars[1].props('color')).toBe('emerald')
|
||||
expect(bars[1].props('resetsAt')).toBe('2026-08-22T00:00:00+08:00')
|
||||
})
|
||||
|
||||
it('labels the refresh control with an explicit action verb, not a data caption', async () => {
|
||||
|
||||
@@ -146,4 +146,61 @@ describe('UsageProgressBar', () => {
|
||||
expect(wrapper.get('.h-1\\.5 > div').attributes('style')).toContain('width: 100%')
|
||||
expect(wrapper.get('.h-1\\.5 > div').classes()).toContain('bg-red-500')
|
||||
})
|
||||
|
||||
it('默认利用率模式按 75/90 阈值提前预警分级', () => {
|
||||
const mountAt = (utilization: number) =>
|
||||
mount(UsageProgressBar, {
|
||||
props: { label: '5h', utilization, color: 'indigo' }
|
||||
})
|
||||
|
||||
// 条形配色:74 绿 / 75 与 89 黄 / 90 红
|
||||
expect(mountAt(74).get('.h-1\\.5 > div').classes()).toContain('bg-green-500')
|
||||
expect(mountAt(75).get('.h-1\\.5 > div').classes()).toContain('bg-amber-500')
|
||||
expect(mountAt(89).get('.h-1\\.5 > div').classes()).toContain('bg-amber-500')
|
||||
expect(mountAt(90).get('.h-1\\.5 > div').classes()).toContain('bg-red-500')
|
||||
|
||||
// 百分比文本同步分级
|
||||
expect(mountAt(74).get('.h-1\\.5 + span').classes()).toContain('text-gray-600')
|
||||
expect(mountAt(75).get('.h-1\\.5 + span').classes()).toContain('text-amber-600')
|
||||
expect(mountAt(89).get('.h-1\\.5 + span').classes()).toContain('text-amber-600')
|
||||
expect(mountAt(90).get('.h-1\\.5 + span').classes()).toContain('text-red-600')
|
||||
})
|
||||
|
||||
it('labelWidth 默认 fixed:标签保持定宽居中,百分比列不变', () => {
|
||||
const wrapper = mount(UsageProgressBar, {
|
||||
props: { label: '5h', utilization: 30, color: 'indigo' }
|
||||
})
|
||||
|
||||
const label = wrapper.get('.gap-1 > span')
|
||||
expect(label.classes()).toContain('w-[32px]')
|
||||
expect(label.classes()).toContain('text-center')
|
||||
expect(label.classes()).not.toContain('max-w-[72px]')
|
||||
|
||||
const percent = wrapper.get('.h-1\\.5 + span')
|
||||
expect(percent.classes()).toContain('w-[32px]')
|
||||
expect(percent.classes()).toContain('text-right')
|
||||
})
|
||||
|
||||
it('labelWidth=auto 时标签限宽截断左对齐,百分比列保持不变', () => {
|
||||
const wrapper = mount(UsageProgressBar, {
|
||||
props: {
|
||||
label: 'Pro/7 天',
|
||||
utilization: 30,
|
||||
color: 'purple',
|
||||
labelWidth: 'auto'
|
||||
}
|
||||
})
|
||||
|
||||
const label = wrapper.get('.gap-1 > span')
|
||||
expect(label.text()).toBe('Pro/7 天')
|
||||
expect(label.classes()).toContain('max-w-[72px]')
|
||||
expect(label.classes()).toContain('truncate')
|
||||
expect(label.classes()).toContain('text-left')
|
||||
expect(label.classes()).not.toContain('w-[32px]')
|
||||
expect(label.classes()).not.toContain('text-center')
|
||||
|
||||
const percent = wrapper.get('.h-1\\.5 + span')
|
||||
expect(percent.classes()).toContain('w-[32px]')
|
||||
expect(percent.classes()).toContain('text-right')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,26 +7,19 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 用量窗口条形图(样式/阈值对齐账号页 CNProviderQuotaCell) -->
|
||||
<!-- 用量窗口条形图(复用账号页 UsageProgressBar:同阈值配色、同倒计时格式) -->
|
||||
<div v-if="snapshot.success && tierRows.length" class="space-y-1">
|
||||
<div v-for="row in tierRows" :key="row.key" class="flex items-center gap-1.5 text-[10px]">
|
||||
<span class="w-14 shrink-0 truncate text-gray-500 dark:text-gray-400" :title="row.title">
|
||||
{{ row.label }}
|
||||
</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"
|
||||
:class="utilizationColor(row.tier.used_percent)"
|
||||
:style="{ width: `${Math.min(100, Math.max(0, row.tier.used_percent))}%` }"
|
||||
/>
|
||||
</div>
|
||||
<span :class="['shrink-0 font-medium', utilizationTextColor(row.tier.used_percent)]">
|
||||
{{ Math.round(row.tier.used_percent) }}%
|
||||
</span>
|
||||
<span v-if="row.tier.reset_at" class="truncate text-gray-400 dark:text-gray-500" :title="row.tier.reset_at">
|
||||
· {{ formatReset(row.tier.reset_at) }}
|
||||
</span>
|
||||
</div>
|
||||
<UsageProgressBar
|
||||
v-for="row in tierRows"
|
||||
:key="row.key"
|
||||
data-testid="monitor-quota-tier"
|
||||
:label="row.label"
|
||||
:title="row.title"
|
||||
label-width="auto"
|
||||
:color="row.color"
|
||||
:utilization="row.tier.used_percent"
|
||||
:resets-at="row.tier.reset_at ?? null"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 余额(国产 payg;支持多币种) -->
|
||||
@@ -50,12 +43,14 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { MonitorQuotaSnapshot, MonitorQuotaTier } from '@/api/admin/channelMonitor'
|
||||
import UsageProgressBar from '@/components/account/UsageProgressBar.vue'
|
||||
|
||||
/**
|
||||
* 配额快照渲染(管理端监控列表/运行结果 + 用户端监控卡片共用)。
|
||||
* 展示形态对齐账号管理侧的用量视图(CNProviderQuotaCell:同阈值配色、
|
||||
* 同倒计时格式);tier 的 Window/Label 是后端约定的机器 token,
|
||||
* 已知 token 走 i18n,未知 token 原样展示(前向兼容)。
|
||||
* 用量条直接复用账号页的 UsageProgressBar(与 CNProviderQuotaCell、
|
||||
* AccountUsageCell 同一组件:同阈值配色、同倒计时格式);tier 的
|
||||
* Window/Label 是后端约定的机器 token,已知 token 走 i18n,未知 token
|
||||
* 原样展示(前向兼容)。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
snapshot?: MonitorQuotaSnapshot | null
|
||||
@@ -63,10 +58,13 @@ const props = defineProps<{
|
||||
|
||||
const { t, te } = useI18n()
|
||||
|
||||
type TierColor = 'indigo' | 'emerald' | 'purple' | 'amber'
|
||||
|
||||
interface QuotaTierRow {
|
||||
key: string
|
||||
label: string
|
||||
title: string
|
||||
color: TierColor
|
||||
tier: MonitorQuotaTier
|
||||
}
|
||||
|
||||
@@ -103,11 +101,15 @@ function tierLabel(tier: MonitorQuotaTier): string {
|
||||
return `${label}/${window}`
|
||||
}
|
||||
|
||||
// tier 配色按数组顺序轮转(UsageProgressBar 支持的色板)。
|
||||
const tierColors: TierColor[] = ['indigo', 'emerald', 'purple', 'amber']
|
||||
|
||||
const tierRows = computed<QuotaTierRow[]>(() =>
|
||||
(props.snapshot?.tiers || []).map((tier, idx) => ({
|
||||
key: `${tier.window}-${tier.label || ''}-${idx}`,
|
||||
label: tierLabel(tier),
|
||||
title: tierLabel(tier),
|
||||
color: tierColors[idx % tierColors.length],
|
||||
tier,
|
||||
})),
|
||||
)
|
||||
@@ -126,31 +128,4 @@ const truncatedError = computed(() => {
|
||||
const error = props.snapshot?.error || t('monitorCommon.quota.unavailable')
|
||||
return error.length > 48 ? `${error.slice(0, 48)}…` : error
|
||||
})
|
||||
|
||||
const utilizationColor = (pct: number) => {
|
||||
if (pct >= 90) return 'bg-red-500'
|
||||
if (pct >= 75) return 'bg-amber-500'
|
||||
return 'bg-emerald-500'
|
||||
}
|
||||
|
||||
const utilizationTextColor = (pct: number) => {
|
||||
if (pct >= 90) return 'text-red-600 dark:text-red-400'
|
||||
if (pct >= 75) return 'text-amber-600 dark:text-amber-400'
|
||||
return 'text-emerald-600 dark:text-emerald-400'
|
||||
}
|
||||
|
||||
// 重置时间相对/绝对简短显示(与账号页一致)。
|
||||
const formatReset = (iso: string) => {
|
||||
const d = new Date(iso)
|
||||
if (isNaN(d.getTime())) return iso
|
||||
const now = Date.now()
|
||||
const diffMs = d.getTime() - now
|
||||
if (diffMs <= 0) return t('monitorCommon.quota.resetSoon')
|
||||
if (diffMs < 3_600_000) return `${Math.max(1, Math.round(diffMs / 60_000))}m`
|
||||
const hours = Math.round(diffMs / 3_600_000)
|
||||
if (hours < 48) return `${hours}h`
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(d.getDate()).padStart(2, '0')
|
||||
return `${mm}-${dd}`
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -42,9 +42,9 @@ describe('MonitorQuotaView', () => {
|
||||
},
|
||||
})
|
||||
|
||||
const rows = wrapper.findAll('[data-testid="monitor-quota-view"] .flex.items-center')
|
||||
const rows = wrapper.findAll('[data-testid="monitor-quota-tier"]')
|
||||
// tier 行只在 success 且有数据时渲染
|
||||
expect(rows.length).toBeGreaterThanOrEqual(3)
|
||||
expect(rows).toHaveLength(3)
|
||||
const text = wrapper.text()
|
||||
// 已知 window token 走 i18n
|
||||
expect(text).toContain('monitorCommon.quota.windows.5h')
|
||||
@@ -59,8 +59,9 @@ describe('MonitorQuotaView', () => {
|
||||
expect(text).toContain('95%')
|
||||
|
||||
const html = wrapper.html()
|
||||
// 阈值配色:≥90 红 / ≥75 黄 / 其余绿(与账号页 CNProviderQuotaCell 对齐)
|
||||
expect(html).toContain('bg-emerald-500')
|
||||
// 阈值配色(三处共用的 UsageProgressBar 统一):≥90 红 / ≥75 黄 / 其余绿
|
||||
// 42.4 → 绿、80 → 黄、95 → 红
|
||||
expect(html).toContain('bg-green-500')
|
||||
expect(html).toContain('bg-amber-500')
|
||||
expect(html).toContain('bg-red-500')
|
||||
})
|
||||
|
||||
@@ -146,14 +146,13 @@ export default {
|
||||
},
|
||||
},
|
||||
balance: 'Balance --',
|
||||
window5h: '5-hour window',
|
||||
windowWeekly: 'Weekly window',
|
||||
window5h: '5h',
|
||||
windowWeekly: '7d',
|
||||
probe: 'Query',
|
||||
probeTooltip: 'Query the provider quota endpoint for 5-hour / weekly rolling window usage',
|
||||
balanceProbeTooltip: 'Query the provider balance endpoint for the account balance',
|
||||
balanceLow: 'Insufficient balance',
|
||||
noBalanceEndpoint: 'This platform has no balance query endpoint',
|
||||
resetSoon: 'reset soon',
|
||||
},
|
||||
types: {
|
||||
oauth: 'OAuth',
|
||||
|
||||
@@ -472,7 +472,6 @@ export default {
|
||||
// Quota snapshot rendering (MonitorQuotaView, shared by admin + user views)
|
||||
quota: {
|
||||
unavailable: 'Quota unavailable',
|
||||
resetSoon: 'resetting',
|
||||
windows: {
|
||||
'5h': '5h',
|
||||
'7d': '7d',
|
||||
|
||||
@@ -349,14 +349,13 @@ export default {
|
||||
},
|
||||
},
|
||||
balance: '余额 --',
|
||||
window5h: '5 小时窗口',
|
||||
windowWeekly: '每周窗口',
|
||||
window5h: '5h',
|
||||
windowWeekly: '7d',
|
||||
probe: '查询',
|
||||
probeTooltip: '请求供应商额度端点,查询 5 小时 / 每周滚动窗口用量',
|
||||
balanceProbeTooltip: '请求供应商余额端点,查询账户余额',
|
||||
balanceLow: '余额不足',
|
||||
noBalanceEndpoint: '该平台暂无余额查询接口',
|
||||
resetSoon: '即将重置',
|
||||
},
|
||||
types: {
|
||||
oauth: 'OAuth',
|
||||
|
||||
@@ -477,7 +477,6 @@ export default {
|
||||
// 配额快照展示(MonitorQuotaView,管理端与用户端共用)
|
||||
quota: {
|
||||
unavailable: '配额信息不可用',
|
||||
resetSoon: '即将重置',
|
||||
windows: {
|
||||
'5h': '5 小时',
|
||||
'7d': '7 天',
|
||||
|
||||
Reference in New Issue
Block a user