- 简化标签:将 "RPD Pro/Flash" 改为 "Pro/Flash",避免文字截断 - 添加账号类型徽章(Free/Pro/Ultra),带颜色区分 - 添加帮助图标(?),悬停显示限流政策和官方文档链接 - 重构显示布局:账号类型 + 两行配额(Pro/Flash) - 移除冗余的 AccountQuotaInfo 组件调用
155 lines
4.6 KiB
Vue
155 lines
4.6 KiB
Vue
<template>
|
||
<div v-if="shouldShowQuota" class="flex items-center gap-2">
|
||
<!-- Tier Badge -->
|
||
<span :class="['badge text-xs px-2 py-0.5 rounded font-medium', tierBadgeClass]">
|
||
{{ tierLabel }}
|
||
</span>
|
||
|
||
<!-- 限流状态 -->
|
||
<span
|
||
v-if="!isRateLimited"
|
||
class="text-xs text-gray-400 dark:text-gray-500"
|
||
>
|
||
{{ t('admin.accounts.gemini.rateLimit.ok') }}
|
||
</span>
|
||
<span
|
||
v-else
|
||
:class="[
|
||
'text-xs font-medium',
|
||
isUrgent
|
||
? 'text-red-600 dark:text-red-400 animate-pulse'
|
||
: 'text-amber-600 dark:text-amber-400'
|
||
]"
|
||
>
|
||
{{ t('admin.accounts.gemini.rateLimit.limited', { time: resetCountdown }) }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref, watch, onUnmounted } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import type { Account, GeminiCredentials } from '@/types'
|
||
|
||
const props = defineProps<{
|
||
account: Account
|
||
}>()
|
||
|
||
const { t } = useI18n()
|
||
|
||
const now = ref(new Date())
|
||
let timer: ReturnType<typeof setInterval> | null = null
|
||
|
||
// 是否为 Code Assist OAuth
|
||
// 判断逻辑与后端保持一致:project_id 存在即为 Code Assist
|
||
const isCodeAssist = computed(() => {
|
||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||
// 显式为 code_assist,或 legacy 情况(oauth_type 为空但 project_id 存在)
|
||
return creds?.oauth_type === 'code_assist' || (!creds?.oauth_type && !!creds?.project_id)
|
||
})
|
||
|
||
// 是否应该显示配额信息
|
||
const shouldShowQuota = computed(() => {
|
||
return props.account.platform === 'gemini'
|
||
})
|
||
|
||
// Tier 标签文本
|
||
const tierLabel = computed(() => {
|
||
if (isCodeAssist.value) {
|
||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||
const tierMap: Record<string, string> = {
|
||
LEGACY: 'Free',
|
||
PRO: 'Pro',
|
||
ULTRA: 'Ultra'
|
||
}
|
||
return tierMap[creds?.tier_id || ''] || 'Unknown'
|
||
}
|
||
return 'Gemini'
|
||
})
|
||
|
||
// Tier Badge 样式
|
||
const tierBadgeClass = computed(() => {
|
||
if (!isCodeAssist.value) {
|
||
return 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||
}
|
||
const creds = props.account.credentials as GeminiCredentials | undefined
|
||
const tierColorMap: Record<string, string> = {
|
||
LEGACY: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400',
|
||
PRO: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
||
ULTRA: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
|
||
}
|
||
return (
|
||
tierColorMap[creds?.tier_id || ''] ||
|
||
'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400'
|
||
)
|
||
})
|
||
|
||
// 是否限流
|
||
const isRateLimited = computed(() => {
|
||
if (!props.account.rate_limit_reset_at) return false
|
||
const resetTime = Date.parse(props.account.rate_limit_reset_at)
|
||
// 防护:如果日期解析失败(NaN),则认为未限流
|
||
if (Number.isNaN(resetTime)) return false
|
||
return resetTime > now.value.getTime()
|
||
})
|
||
|
||
// 倒计时文本
|
||
const resetCountdown = computed(() => {
|
||
if (!props.account.rate_limit_reset_at) return ''
|
||
const resetTime = Date.parse(props.account.rate_limit_reset_at)
|
||
// 防护:如果日期解析失败,显示 "-"
|
||
if (Number.isNaN(resetTime)) return '-'
|
||
|
||
const diffMs = resetTime - now.value.getTime()
|
||
if (diffMs <= 0) return t('admin.accounts.gemini.rateLimit.now')
|
||
|
||
const diffSeconds = Math.floor(diffMs / 1000)
|
||
const diffMinutes = Math.floor(diffSeconds / 60)
|
||
const diffHours = Math.floor(diffMinutes / 60)
|
||
|
||
if (diffMinutes < 1) return `${diffSeconds}s`
|
||
if (diffHours < 1) {
|
||
const secs = diffSeconds % 60
|
||
return `${diffMinutes}m ${secs}s`
|
||
}
|
||
const mins = diffMinutes % 60
|
||
return `${diffHours}h ${mins}m`
|
||
})
|
||
|
||
// 是否紧急(< 1分钟)
|
||
const isUrgent = computed(() => {
|
||
if (!props.account.rate_limit_reset_at) return false
|
||
const resetTime = Date.parse(props.account.rate_limit_reset_at)
|
||
// 防护:如果日期解析失败,返回 false
|
||
if (Number.isNaN(resetTime)) return false
|
||
|
||
const diffMs = resetTime - now.value.getTime()
|
||
return diffMs > 0 && diffMs < 60000
|
||
})
|
||
|
||
// 监听限流状态,动态启动/停止定时器
|
||
watch(
|
||
() => isRateLimited.value,
|
||
(limited) => {
|
||
if (limited && !timer) {
|
||
// 进入限流状态,启动定时器
|
||
timer = setInterval(() => {
|
||
now.value = new Date()
|
||
}, 1000)
|
||
} else if (!limited && timer) {
|
||
// 解除限流,停止定时器
|
||
clearInterval(timer)
|
||
timer = null
|
||
}
|
||
},
|
||
{ immediate: true } // 立即执行,确保挂载时已限流的情况也能启动定时器
|
||
)
|
||
|
||
onUnmounted(() => {
|
||
if (timer !== null) {
|
||
clearInterval(timer)
|
||
timer = null
|
||
}
|
||
})
|
||
</script>
|