Files
sub2api/frontend/src/components/account/UsageProgressBar.vue

162 lines
4.7 KiB
Vue
Raw Normal View History

2025-12-18 13:50:39 +08:00
<template>
<div>
<!-- Window stats row (above progress bar) -->
<div
v-if="windowStats && (windowStats.requests > 0 || windowStats.tokens > 0)"
class="mb-0.5 flex items-center"
>
<div class="flex items-center gap-1.5 text-[9px] text-gray-500 dark:text-gray-400">
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800">
{{ formatRequests }} req
</span>
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800">
{{ formatTokens }}
</span>
<span class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800" :title="t('usage.accountBilled')">
A ${{ formatAccountCost }}
</span>
<span
v-if="windowStats?.user_cost != null"
class="rounded bg-gray-100 px-1.5 py-0.5 dark:bg-gray-800"
:title="t('usage.userBilled')"
>
U ${{ formatUserCost }}
</span>
</div>
</div>
<!-- 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 }}
</span>
<!-- Progress bar container -->
<div class="h-1.5 w-8 shrink-0 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
<div
:class="['h-full transition-all duration-300', barClass]"
:style="{ width: barWidth }"
></div>
</div>
<!-- Percentage -->
<span :class="['w-[32px] shrink-0 text-right text-[10px] font-medium', textClass]">
{{ displayPercent }}
</span>
<!-- Reset time -->
<span v-if="resetsAt" class="shrink-0 text-[10px] text-gray-400">
{{ formatResetTime }}
</span>
</div>
2025-12-18 13:50:39 +08:00
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
2025-12-18 13:50:39 +08:00
import type { WindowStats } from '@/types'
import { formatCompactNumber } from '@/utils/format'
2025-12-18 13:50:39 +08:00
const props = defineProps<{
label: string
utilization: number // Percentage (0-100+)
2025-12-18 13:50:39 +08:00
resetsAt?: string | null
color: 'indigo' | 'emerald' | 'purple' | 'amber'
2025-12-18 13:50:39 +08:00
windowStats?: WindowStats | null
}>()
const { t } = useI18n()
2025-12-18 13:50:39 +08:00
// Label background colors
const labelClass = computed(() => {
const colors = {
indigo: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300',
emerald: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',
purple: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300',
amber: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300'
2025-12-18 13:50:39 +08:00
}
return colors[props.color]
})
// Progress bar color based on utilization
const barClass = computed(() => {
if (props.utilization >= 100) {
return 'bg-red-500'
} else if (props.utilization >= 80) {
return 'bg-amber-500'
} else {
return 'bg-green-500'
}
})
// Text color based on utilization
const textClass = computed(() => {
if (props.utilization >= 100) {
return 'text-red-600 dark:text-red-400'
} else if (props.utilization >= 80) {
return 'text-amber-600 dark:text-amber-400'
} else {
return 'text-gray-600 dark:text-gray-400'
}
})
// Bar width (capped at 100%)
const barWidth = computed(() => {
return `${Math.min(props.utilization, 100)}%`
})
// Display percentage (cap at 999% for readability)
const displayPercent = computed(() => {
const percent = Math.round(props.utilization)
return percent > 999 ? '>999%' : `${percent}%`
})
// Format reset time
const formatResetTime = computed(() => {
if (!props.resetsAt) return '-'
2025-12-18 13:50:39 +08:00
const date = new Date(props.resetsAt)
const now = new Date()
const diffMs = date.getTime() - now.getTime()
if (diffMs <= 0) return '现在'
2025-12-18 13:50:39 +08:00
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
const diffMins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
if (diffHours >= 24) {
const days = Math.floor(diffHours / 24)
return `${days}d ${diffHours % 24}h`
} else if (diffHours > 0) {
return `${diffHours}h ${diffMins}m`
} else {
return `${diffMins}m`
}
})
// Window stats formatters
const formatRequests = computed(() => {
if (!props.windowStats) return ''
return formatCompactNumber(props.windowStats.requests, { allowBillions: false })
})
const formatTokens = computed(() => {
if (!props.windowStats) return ''
return formatCompactNumber(props.windowStats.tokens)
})
const formatAccountCost = computed(() => {
if (!props.windowStats) return '0.00'
return props.windowStats.cost.toFixed(2)
})
const formatUserCost = computed(() => {
if (!props.windowStats || props.windowStats.user_cost == null) return '0.00'
return props.windowStats.user_cost.toFixed(2)
})
2025-12-18 13:50:39 +08:00
</script>