mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
style: 优化api-stats布局
This commit is contained in:
@@ -558,7 +558,6 @@ class ClaudeAccountService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: accountData.id,
|
id: accountData.id,
|
||||||
name: accountData.name,
|
|
||||||
accountType: accountData.accountType || 'shared',
|
accountType: accountData.accountType || 'shared',
|
||||||
platform: accountData.platform || 'claude',
|
platform: accountData.platform || 'claude',
|
||||||
isActive: accountData.isActive === 'true',
|
isActive: accountData.isActive === 'true',
|
||||||
|
|||||||
@@ -833,7 +833,6 @@ async function getAccountOverview(accountId) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: accountData.id,
|
id: accountData.id,
|
||||||
name: accountData.name,
|
|
||||||
accountType: accountData.accountType || 'shared',
|
accountType: accountData.accountType || 'shared',
|
||||||
platform: accountData.platform || 'openai',
|
platform: accountData.platform || 'openai',
|
||||||
isActive: accountData.isActive === 'true',
|
isActive: accountData.isActive === 'true',
|
||||||
|
|||||||
@@ -1,403 +1,397 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-6 grid grid-cols-1 gap-4 md:mb-8 md:gap-6 lg:grid-cols-2">
|
<div class="space-y-6 md:space-y-8">
|
||||||
<!-- API Key 基本信息 / 批量查询概要 -->
|
<div class="grid grid-cols-1 gap-4 md:gap-6 lg:grid-cols-2 lg:items-start">
|
||||||
<div class="card p-4 md:p-6">
|
<!-- API Key 基本信息 / 批量查询概要 -->
|
||||||
<h3
|
<div class="card p-4 md:p-6">
|
||||||
class="mb-3 flex items-center text-lg font-bold text-gray-900 dark:text-gray-100 md:mb-4 md:text-xl"
|
<h3
|
||||||
>
|
class="mb-3 flex items-center text-lg font-bold text-gray-900 dark:text-gray-100 md:mb-4 md:text-xl"
|
||||||
<i
|
|
||||||
class="mr-2 text-sm md:mr-3 md:text-base"
|
|
||||||
:class="
|
|
||||||
multiKeyMode ? 'fas fa-layer-group text-purple-500' : 'fas fa-info-circle text-blue-500'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
{{ multiKeyMode ? '批量查询概要' : 'API Key 信息' }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- 多 Key 模式下的概要信息 -->
|
|
||||||
<div v-if="multiKeyMode && aggregatedStats" class="space-y-2 md:space-y-3">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">查询 Keys 数</span>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">
|
|
||||||
{{ aggregatedStats.totalKeys }} 个
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">有效 Keys 数</span>
|
|
||||||
<span class="text-sm font-medium text-green-600 md:text-base">
|
|
||||||
<i class="fas fa-check-circle mr-1 text-xs md:text-sm" />
|
|
||||||
{{ aggregatedStats.activeKeys }} 个
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="invalidKeys.length > 0" class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">无效 Keys 数</span>
|
|
||||||
<span class="text-sm font-medium text-red-600 md:text-base">
|
|
||||||
<i class="fas fa-times-circle mr-1 text-xs md:text-sm" />
|
|
||||||
{{ invalidKeys.length }} 个
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">总请求数</span>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">
|
|
||||||
{{ formatNumber(aggregatedStats.usage.requests) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">总 Token 数</span>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">
|
|
||||||
{{ formatNumber(aggregatedStats.usage.allTokens) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">总费用</span>
|
|
||||||
<span class="text-sm font-medium text-indigo-600 md:text-base">
|
|
||||||
{{ aggregatedStats.usage.formattedCost }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 各 Key 贡献占比(可选) -->
|
|
||||||
<div
|
|
||||||
v-if="individualStats.length > 1"
|
|
||||||
class="border-t border-gray-200 pt-2 dark:border-gray-700"
|
|
||||||
>
|
>
|
||||||
<div class="mb-2 text-xs text-gray-500 dark:text-gray-400">各 Key 贡献占比</div>
|
<i
|
||||||
<div class="space-y-1">
|
class="mr-2 text-sm md:mr-3 md:text-base"
|
||||||
<div
|
:class="
|
||||||
v-for="stat in topContributors"
|
multiKeyMode
|
||||||
:key="stat.apiId"
|
? 'fas fa-layer-group text-purple-500'
|
||||||
class="flex items-center justify-between text-xs"
|
: 'fas fa-info-circle text-blue-500'
|
||||||
>
|
"
|
||||||
<span class="truncate text-gray-600 dark:text-gray-400">{{ stat.name }}</span>
|
/>
|
||||||
<span class="text-gray-900 dark:text-gray-100"
|
{{ multiKeyMode ? '批量查询概要' : 'API Key 信息' }}
|
||||||
>{{ calculateContribution(stat) }}%</span
|
</h3>
|
||||||
|
|
||||||
|
<!-- 多 Key 模式下的概要信息 -->
|
||||||
|
<div v-if="multiKeyMode && aggregatedStats" class="space-y-2 md:space-y-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">查询 Keys 数</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">
|
||||||
|
{{ aggregatedStats.totalKeys }} 个
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">有效 Keys 数</span>
|
||||||
|
<span class="text-sm font-medium text-green-600 md:text-base">
|
||||||
|
<i class="fas fa-check-circle mr-1 text-xs md:text-sm" />
|
||||||
|
{{ aggregatedStats.activeKeys }} 个
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="invalidKeys.length > 0" class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">无效 Keys 数</span>
|
||||||
|
<span class="text-sm font-medium text-red-600 md:text-base">
|
||||||
|
<i class="fas fa-times-circle mr-1 text-xs md:text-sm" />
|
||||||
|
{{ invalidKeys.length }} 个
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">总请求数</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">
|
||||||
|
{{ formatNumber(aggregatedStats.usage.requests) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">总 Token 数</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">
|
||||||
|
{{ formatNumber(aggregatedStats.usage.allTokens) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">总费用</span>
|
||||||
|
<span class="text-sm font-medium text-indigo-600 md:text-base">
|
||||||
|
{{ aggregatedStats.usage.formattedCost }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 各 Key 贡献占比(可选) -->
|
||||||
|
<div
|
||||||
|
v-if="individualStats.length > 1"
|
||||||
|
class="border-t border-gray-200 pt-2 dark:border-gray-700"
|
||||||
|
>
|
||||||
|
<div class="mb-2 text-xs text-gray-500 dark:text-gray-400">各 Key 贡献占比</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div
|
||||||
|
v-for="stat in topContributors"
|
||||||
|
:key="stat.apiId"
|
||||||
|
class="flex items-center justify-between text-xs"
|
||||||
>
|
>
|
||||||
|
<span class="truncate text-gray-600 dark:text-gray-400">{{ stat.name }}</span>
|
||||||
|
<span class="text-gray-900 dark:text-gray-100"
|
||||||
|
>{{ calculateContribution(stat) }}%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 单 Key 模式下的详细信息 -->
|
||||||
|
<div v-else class="space-y-2 md:space-y-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">名称</span>
|
||||||
|
<span
|
||||||
|
class="break-all text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base"
|
||||||
|
>{{ statsData.name }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">状态</span>
|
||||||
|
<span
|
||||||
|
class="text-sm font-medium md:text-base"
|
||||||
|
:class="statsData.isActive ? 'text-green-600' : 'text-red-600'"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="mr-1 text-xs md:text-sm"
|
||||||
|
:class="statsData.isActive ? 'fas fa-check-circle' : 'fas fa-times-circle'"
|
||||||
|
/>
|
||||||
|
{{ statsData.isActive ? '活跃' : '已停用' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">权限</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">{{
|
||||||
|
formatPermissions(statsData.permissions)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">创建时间</span>
|
||||||
|
<span
|
||||||
|
class="break-all text-xs font-medium text-gray-900 dark:text-gray-100 md:text-base"
|
||||||
|
>{{ formatDate(statsData.createdAt) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<span class="mt-1 flex-shrink-0 text-sm text-gray-600 dark:text-gray-400 md:text-base"
|
||||||
|
>过期时间</span
|
||||||
|
>
|
||||||
|
<!-- 未激活状态 -->
|
||||||
|
<div
|
||||||
|
v-if="statsData.expirationMode === 'activation' && !statsData.isActivated"
|
||||||
|
class="text-sm font-medium text-amber-600 dark:text-amber-500 md:text-base"
|
||||||
|
>
|
||||||
|
<i class="fas fa-pause-circle mr-1 text-xs md:text-sm" />
|
||||||
|
未激活
|
||||||
|
<span class="ml-1 text-xs text-gray-500 dark:text-gray-400"
|
||||||
|
>(首次使用后
|
||||||
|
{{ statsData.activationDays || (statsData.activationUnit === 'hours' ? 24 : 30)
|
||||||
|
}}{{ statsData.activationUnit === 'hours' ? '小时' : '天' }}过期)</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<!-- 已设置过期时间 -->
|
||||||
|
<div v-else-if="statsData.expiresAt" class="text-right">
|
||||||
|
<div
|
||||||
|
v-if="isApiKeyExpired(statsData.expiresAt)"
|
||||||
|
class="text-sm font-medium text-red-600 md:text-base"
|
||||||
|
>
|
||||||
|
<i class="fas fa-exclamation-circle mr-1 text-xs md:text-sm" />
|
||||||
|
已过期
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="isApiKeyExpiringSoon(statsData.expiresAt)"
|
||||||
|
class="break-all text-xs font-medium text-orange-600 md:text-base"
|
||||||
|
>
|
||||||
|
<i class="fas fa-clock mr-1 text-xs md:text-sm" />
|
||||||
|
{{ formatExpireDate(statsData.expiresAt) }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="break-all text-xs font-medium text-gray-900 dark:text-gray-100 md:text-base"
|
||||||
|
>
|
||||||
|
{{ formatExpireDate(statsData.expiresAt) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 永不过期 -->
|
||||||
|
<div v-else class="text-sm font-medium text-gray-400 dark:text-gray-500 md:text-base">
|
||||||
|
<i class="fas fa-infinity mr-1 text-xs md:text-sm" />
|
||||||
|
永不过期
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 单 Key 模式下的详细信息 -->
|
<!-- 使用统计概览 -->
|
||||||
<div v-else class="space-y-2 md:space-y-3">
|
<div class="card p-4 md:p-6">
|
||||||
<div class="flex items-center justify-between">
|
<h3
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">名称</span>
|
class="mb-3 flex flex-col text-lg font-bold text-gray-900 dark:text-gray-100 sm:flex-row sm:items-center md:mb-4 md:text-xl"
|
||||||
<span
|
|
||||||
class="break-all text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base"
|
|
||||||
>{{ statsData.name }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">状态</span>
|
|
||||||
<span
|
|
||||||
class="text-sm font-medium md:text-base"
|
|
||||||
:class="statsData.isActive ? 'text-green-600' : 'text-red-600'"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="mr-1 text-xs md:text-sm"
|
|
||||||
:class="statsData.isActive ? 'fas fa-check-circle' : 'fas fa-times-circle'"
|
|
||||||
/>
|
|
||||||
{{ statsData.isActive ? '活跃' : '已停用' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">权限</span>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 md:text-base">{{
|
|
||||||
formatPermissions(statsData.permissions)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400 md:text-base">创建时间</span>
|
|
||||||
<span
|
|
||||||
class="break-all text-xs font-medium text-gray-900 dark:text-gray-100 md:text-base"
|
|
||||||
>{{ formatDate(statsData.createdAt) }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<span class="mt-1 flex-shrink-0 text-sm text-gray-600 dark:text-gray-400 md:text-base"
|
|
||||||
>过期时间</span
|
|
||||||
>
|
|
||||||
<!-- 未激活状态 -->
|
|
||||||
<div
|
|
||||||
v-if="statsData.expirationMode === 'activation' && !statsData.isActivated"
|
|
||||||
class="text-sm font-medium text-amber-600 dark:text-amber-500 md:text-base"
|
|
||||||
>
|
|
||||||
<i class="fas fa-pause-circle mr-1 text-xs md:text-sm" />
|
|
||||||
未激活
|
|
||||||
<span class="ml-1 text-xs text-gray-500 dark:text-gray-400"
|
|
||||||
>(首次使用后
|
|
||||||
{{ statsData.activationDays || (statsData.activationUnit === 'hours' ? 24 : 30)
|
|
||||||
}}{{ statsData.activationUnit === 'hours' ? '小时' : '天' }}过期)</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<!-- 已设置过期时间 -->
|
|
||||||
<div v-else-if="statsData.expiresAt" class="text-right">
|
|
||||||
<div
|
|
||||||
v-if="isApiKeyExpired(statsData.expiresAt)"
|
|
||||||
class="text-sm font-medium text-red-600 md:text-base"
|
|
||||||
>
|
|
||||||
<i class="fas fa-exclamation-circle mr-1 text-xs md:text-sm" />
|
|
||||||
已过期
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else-if="isApiKeyExpiringSoon(statsData.expiresAt)"
|
|
||||||
class="break-all text-xs font-medium text-orange-600 md:text-base"
|
|
||||||
>
|
|
||||||
<i class="fas fa-clock mr-1 text-xs md:text-sm" />
|
|
||||||
{{ formatExpireDate(statsData.expiresAt) }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="break-all text-xs font-medium text-gray-900 dark:text-gray-100 md:text-base"
|
|
||||||
>
|
|
||||||
{{ formatExpireDate(statsData.expiresAt) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 永不过期 -->
|
|
||||||
<div v-else class="text-sm font-medium text-gray-400 dark:text-gray-500 md:text-base">
|
|
||||||
<i class="fas fa-infinity mr-1 text-xs md:text-sm" />
|
|
||||||
永不过期
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="boundAccountList.length > 0"
|
|
||||||
class="mt-4 rounded-2xl border border-indigo-100/60 bg-indigo-50/60 p-4 dark:border-indigo-500/40 dark:bg-indigo-500/10"
|
|
||||||
>
|
>
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<span class="flex items-center">
|
||||||
<div class="flex items-center gap-2">
|
<i class="fas fa-chart-bar mr-2 text-sm text-green-500 md:mr-3 md:text-base" />
|
||||||
<i class="fas fa-link text-sm text-indigo-500 md:text-base" />
|
使用统计概览
|
||||||
<span class="text-sm font-semibold text-indigo-900 dark:text-indigo-200 md:text-base"
|
</span>
|
||||||
>专属账号运行状态</span
|
<span class="text-xs font-normal text-gray-600 dark:text-gray-400 sm:ml-2 md:text-sm"
|
||||||
>
|
>({{ statsPeriod === 'daily' ? '今日' : '本月' }})</span
|
||||||
|
>
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-2 gap-3 md:gap-4">
|
||||||
|
<div class="stat-card text-center">
|
||||||
|
<div class="text-lg font-bold text-green-600 md:text-3xl">
|
||||||
|
{{ formatNumber(currentPeriodData.requests) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
||||||
|
{{ statsPeriod === 'daily' ? '今日' : '本月' }}请求数
|
||||||
</div>
|
</div>
|
||||||
<span
|
|
||||||
class="rounded-full bg-white/70 px-2 py-0.5 text-xs font-medium text-indigo-500 shadow-sm dark:bg-slate-900/40 dark:text-indigo-200"
|
|
||||||
>实时速览</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-card text-center">
|
||||||
<div class="space-y-3 md:space-y-4">
|
<div class="text-lg font-bold text-blue-600 md:text-3xl">
|
||||||
<div
|
{{ formatNumber(currentPeriodData.allTokens) }}
|
||||||
v-for="account in boundAccountList"
|
</div>
|
||||||
:key="account.id"
|
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
||||||
class="rounded-xl bg-white/80 p-3 shadow-sm backdrop-blur dark:bg-slate-900/50 md:p-4"
|
{{ statsPeriod === 'daily' ? '今日' : '本月' }}Token数
|
||||||
>
|
</div>
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="stat-card text-center">
|
||||||
<div
|
<div class="text-lg font-bold text-purple-600 md:text-3xl">
|
||||||
:class="[
|
{{ currentPeriodData.formattedCost || '$0.000000' }}
|
||||||
'flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full text-white shadow-inner',
|
</div>
|
||||||
account.platform === 'claude'
|
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
||||||
? 'bg-gradient-to-br from-purple-500 to-purple-600'
|
{{ statsPeriod === 'daily' ? '今日' : '本月' }}费用
|
||||||
: 'bg-gradient-to-br from-sky-500 to-indigo-500'
|
</div>
|
||||||
]"
|
</div>
|
||||||
>
|
<div class="stat-card text-center">
|
||||||
<i :class="account.platform === 'claude' ? 'fas fa-meteor' : 'fas fa-robot'" />
|
<div class="text-lg font-bold text-yellow-600 md:text-3xl">
|
||||||
</div>
|
{{ formatNumber(currentPeriodData.inputTokens) }}
|
||||||
<div>
|
</div>
|
||||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
||||||
{{ account.name || '未命名账号' }}
|
{{ statsPeriod === 'daily' ? '今日' : '本月' }}输入Token
|
||||||
</div>
|
|
||||||
<div class="mt-1 flex items-center gap-2 text-[11px]">
|
|
||||||
<span
|
|
||||||
v-if="account.platform === 'claude'"
|
|
||||||
class="inline-flex items-center rounded-full bg-purple-100 px-2 py-0.5 font-medium text-purple-700 dark:bg-purple-500/20 dark:text-purple-200"
|
|
||||||
>Claude 专属</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 font-medium text-blue-700 dark:bg-blue-500/20 dark:text-blue-200"
|
|
||||||
>OpenAI 专属</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="getRateLimitDisplay(account.rateLimitStatus)"
|
|
||||||
class="text-xs font-semibold"
|
|
||||||
:class="getRateLimitDisplay(account.rateLimitStatus).class"
|
|
||||||
>
|
|
||||||
<i class="fas fa-tachometer-alt mr-1" />
|
|
||||||
{{ getRateLimitDisplay(account.rateLimitStatus).text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="account.platform === 'claude'" class="mt-3 space-y-3">
|
|
||||||
<div v-if="account.sessionWindow?.hasActiveWindow" class="space-y-2">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="h-2 w-32 rounded-full bg-gray-200 dark:bg-gray-700">
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'h-2 rounded-full transition-all duration-300',
|
|
||||||
getSessionProgressBarClass(
|
|
||||||
account.sessionWindow?.sessionWindowStatus,
|
|
||||||
account
|
|
||||||
)
|
|
||||||
]"
|
|
||||||
:style="{
|
|
||||||
width: `${Math.min(
|
|
||||||
100,
|
|
||||||
Math.max(0, account.sessionWindow?.progress || 0)
|
|
||||||
)}%`
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span class="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
|
||||||
{{
|
|
||||||
Math.min(
|
|
||||||
100,
|
|
||||||
Math.max(0, Math.round(account.sessionWindow?.progress || 0))
|
|
||||||
)
|
|
||||||
}}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex flex-wrap items-center gap-3 text-[11px] text-gray-600 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
formatSessionWindowRange(
|
|
||||||
account.sessionWindow?.windowStart,
|
|
||||||
account.sessionWindow?.windowEnd
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="account.sessionWindow?.remainingTime > 0"
|
|
||||||
class="font-medium text-indigo-600 dark:text-indigo-400"
|
|
||||||
>
|
|
||||||
剩余 {{ formatSessionRemaining(account.sessionWindow.remainingTime) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="rounded-lg bg-white/60 px-3 py-2 text-xs text-gray-500 dark:bg-slate-800/60 dark:text-gray-400"
|
|
||||||
>
|
|
||||||
暂无活跃会话窗口
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="account.platform === 'openai'" class="mt-3 space-y-3">
|
|
||||||
<div v-if="account.codexUsage" class="space-y-3">
|
|
||||||
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-700/70">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span
|
|
||||||
class="inline-flex min-w-[34px] justify-center rounded-full bg-indigo-100 px-2 py-0.5 text-[11px] font-medium text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-300"
|
|
||||||
>
|
|
||||||
{{ getCodexWindowLabel('primary') }}
|
|
||||||
</span>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="h-2 flex-1 rounded-full bg-gray-200 dark:bg-gray-600">
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'h-2 rounded-full transition-all duration-300',
|
|
||||||
getCodexUsageBarClass(account.codexUsage.primary)
|
|
||||||
]"
|
|
||||||
:style="{ width: getCodexUsageWidth(account.codexUsage.primary) }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="w-12 text-right text-xs font-semibold text-gray-800 dark:text-gray-100"
|
|
||||||
>
|
|
||||||
{{ formatCodexUsagePercent(account.codexUsage.primary) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
|
||||||
重置剩余 {{ formatCodexRemaining(account.codexUsage.primary) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-700/70">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span
|
|
||||||
class="inline-flex min-w-[34px] justify-center rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-medium text-blue-600 dark:bg-blue-500/20 dark:text-blue-200"
|
|
||||||
>
|
|
||||||
{{ getCodexWindowLabel('secondary') }}
|
|
||||||
</span>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div class="h-2 flex-1 rounded-full bg-gray-200 dark:bg-gray-600">
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'h-2 rounded-full transition-all duration-300',
|
|
||||||
getCodexUsageBarClass(account.codexUsage.secondary)
|
|
||||||
]"
|
|
||||||
:style="{ width: getCodexUsageWidth(account.codexUsage.secondary) }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="w-12 text-right text-xs font-semibold text-gray-800 dark:text-gray-100"
|
|
||||||
>
|
|
||||||
{{ formatCodexUsagePercent(account.codexUsage.secondary) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
|
||||||
重置剩余 {{ formatCodexRemaining(account.codexUsage.secondary) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="rounded-lg bg-white/60 px-3 py-2 text-xs text-gray-500 dark:bg-slate-800/60 dark:text-gray-400"
|
|
||||||
>
|
|
||||||
暂无额度使用数据
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 使用统计概览 -->
|
<div v-if="boundAccountList.length > 0" class="card p-4 md:p-6">
|
||||||
<div class="card p-4 md:p-6">
|
<div class="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
<h3
|
<div class="flex items-center gap-2">
|
||||||
class="mb-3 flex flex-col text-lg font-bold text-gray-900 dark:text-gray-100 sm:flex-row sm:items-center md:mb-4 md:text-xl"
|
<i class="fas fa-link text-sm text-indigo-500 md:text-base" />
|
||||||
>
|
<span class="text-sm font-semibold text-indigo-900 dark:text-indigo-200 md:text-base"
|
||||||
<span class="flex items-center">
|
>专属账号运行状态</span
|
||||||
<i class="fas fa-chart-bar mr-2 text-sm text-green-500 md:mr-3 md:text-base" />
|
>
|
||||||
使用统计概览
|
</div>
|
||||||
</span>
|
<span
|
||||||
<span class="text-xs font-normal text-gray-600 dark:text-gray-400 sm:ml-2 md:text-sm"
|
class="rounded-full bg-indigo-100 px-2 py-0.5 text-xs font-semibold text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-200"
|
||||||
>({{ statsPeriod === 'daily' ? '今日' : '本月' }})</span
|
>实时速览</span
|
||||||
>
|
>
|
||||||
</h3>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-3 md:gap-4">
|
<div class="grid grid-cols-1 gap-3 md:gap-4 lg:grid-cols-2">
|
||||||
<div class="stat-card text-center">
|
<div
|
||||||
<div class="text-lg font-bold text-green-600 md:text-3xl">
|
v-for="account in boundAccountList"
|
||||||
{{ formatNumber(currentPeriodData.requests) }}
|
:key="account.id || account.key"
|
||||||
|
class="rounded-xl border border-white/60 bg-white/85 p-4 shadow-sm backdrop-blur dark:border-slate-700/70 dark:bg-slate-900/60"
|
||||||
|
>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full text-white shadow-inner',
|
||||||
|
account.platform === 'claude'
|
||||||
|
? 'bg-gradient-to-br from-purple-500 to-purple-600'
|
||||||
|
: 'bg-gradient-to-br from-sky-500 to-indigo-500'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<i :class="account.platform === 'claude' ? 'fas fa-meteor' : 'fas fa-robot'" />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ getAccountTitle(account) }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-[11px]">
|
||||||
|
<span
|
||||||
|
v-if="account.platform === 'claude'"
|
||||||
|
class="inline-flex items-center rounded-full bg-purple-100 px-2 py-0.5 font-medium text-purple-700 dark:bg-purple-500/20 dark:text-purple-200"
|
||||||
|
>Claude 专用</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 font-medium text-blue-700 dark:bg-blue-500/20 dark:text-blue-200"
|
||||||
|
>OpenAI 专用</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="getRateLimitDisplay(account.rateLimitStatus)"
|
||||||
|
class="text-xs font-semibold"
|
||||||
|
:class="getRateLimitDisplay(account.rateLimitStatus).class"
|
||||||
|
>
|
||||||
|
<i class="fas fa-tachometer-alt mr-1" />
|
||||||
|
{{ getRateLimitDisplay(account.rateLimitStatus).text }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
|
||||||
{{ statsPeriod === 'daily' ? '今日' : '本月' }}请求数
|
<div v-if="account.platform === 'claude'" class="mt-4 space-y-2">
|
||||||
|
<div v-if="account.sessionWindow?.hasActiveWindow" class="space-y-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="h-2 flex-1 rounded-full bg-gray-200 dark:bg-gray-700">
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'h-2 rounded-full transition-all duration-300',
|
||||||
|
getSessionProgressBarClass(
|
||||||
|
account.sessionWindow?.sessionWindowStatus,
|
||||||
|
account
|
||||||
|
)
|
||||||
|
]"
|
||||||
|
:style="{
|
||||||
|
width: `${Math.min(100, Math.max(0, account.sessionWindow?.progress || 0))}%`
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
||||||
|
{{
|
||||||
|
Math.min(100, Math.max(0, Math.round(account.sessionWindow?.progress || 0)))
|
||||||
|
}}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap items-center gap-3 text-[11px] text-gray-600 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
formatSessionWindowRange(
|
||||||
|
account.sessionWindow?.windowStart,
|
||||||
|
account.sessionWindow?.windowEnd
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="account.sessionWindow?.remainingTime > 0"
|
||||||
|
class="font-medium text-indigo-600 dark:text-indigo-400"
|
||||||
|
>
|
||||||
|
剩余 {{ formatSessionRemaining(account.sessionWindow.remainingTime) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="rounded-lg bg-white/70 px-3 py-2 text-xs text-gray-500 dark:bg-slate-800/60 dark:text-gray-400"
|
||||||
|
>
|
||||||
|
暂无活跃会话窗口
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="stat-card text-center">
|
<div v-else-if="account.platform === 'openai'" class="mt-4 space-y-3">
|
||||||
<div class="text-lg font-bold text-blue-600 md:text-3xl">
|
<div v-if="account.codexUsage" class="space-y-3">
|
||||||
{{ formatNumber(currentPeriodData.allTokens) }}
|
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-700/70">
|
||||||
</div>
|
<div class="flex items-center gap-2">
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
<span
|
||||||
{{ statsPeriod === 'daily' ? '今日' : '本月' }}Token数
|
class="inline-flex min-w-[34px] justify-center rounded-full bg-indigo-100 px-2 py-0.5 text-[11px] font-medium text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-300"
|
||||||
</div>
|
>
|
||||||
</div>
|
{{ getCodexWindowLabel('primary') }}
|
||||||
<div class="stat-card text-center">
|
</span>
|
||||||
<div class="text-lg font-bold text-purple-600 md:text-3xl">
|
<div class="flex-1">
|
||||||
{{ currentPeriodData.formattedCost || '$0.000000' }}
|
<div class="flex items-center gap-2">
|
||||||
</div>
|
<div class="h-2 flex-1 rounded-full bg-gray-200 dark:bg-gray-600">
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
<div
|
||||||
{{ statsPeriod === 'daily' ? '今日' : '本月' }}费用
|
:class="[
|
||||||
</div>
|
'h-2 rounded-full transition-all duration-300',
|
||||||
</div>
|
getCodexUsageBarClass(account.codexUsage.primary)
|
||||||
<div class="stat-card text-center">
|
]"
|
||||||
<div class="text-lg font-bold text-yellow-600 md:text-3xl">
|
:style="{ width: getCodexUsageWidth(account.codexUsage.primary) }"
|
||||||
{{ formatNumber(currentPeriodData.inputTokens) }}
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-400 md:text-sm">
|
<span
|
||||||
{{ statsPeriod === 'daily' ? '今日' : '本月' }}输入Token
|
class="w-12 text-right text-xs font-semibold text-gray-800 dark:text-gray-100"
|
||||||
|
>
|
||||||
|
{{ formatCodexUsagePercent(account.codexUsage.primary) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
|
重置剩余 {{ formatCodexRemaining(account.codexUsage.primary) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-700/70">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
class="inline-flex min-w-[34px] justify-center rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-medium text-blue-600 dark:bg-blue-500/20 dark:text-blue-200"
|
||||||
|
>
|
||||||
|
{{ getCodexWindowLabel('secondary') }}
|
||||||
|
</span>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="h-2 flex-1 rounded-full bg-gray-200 dark:bg-gray-600">
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'h-2 rounded-full transition-all duration-300',
|
||||||
|
getCodexUsageBarClass(account.codexUsage.secondary)
|
||||||
|
]"
|
||||||
|
:style="{ width: getCodexUsageWidth(account.codexUsage.secondary) }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="w-12 text-right text-xs font-semibold text-gray-800 dark:text-gray-100"
|
||||||
|
>
|
||||||
|
{{ formatCodexUsagePercent(account.codexUsage.secondary) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
|
重置剩余 {{ formatCodexRemaining(account.codexUsage.secondary) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="rounded-lg bg-white/70 px-3 py-2 text-xs text-gray-500 dark:bg-slate-800/60 dark:text-gray-400"
|
||||||
|
>
|
||||||
|
暂无额度使用数据
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -528,7 +522,20 @@ const boundAccountList = computed(() => {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
// 将分钟格式化为易读文本
|
// 专属账号标题
|
||||||
|
const getAccountTitle = (account) => {
|
||||||
|
if (!account) {
|
||||||
|
return '专属账号'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.platform === 'openai') {
|
||||||
|
return 'OpenAI 专属账号'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Claude 专属账号'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化限流时间
|
||||||
const formatRateLimitTime = (minutes) => {
|
const formatRateLimitTime = (minutes) => {
|
||||||
if (!minutes || minutes <= 0) {
|
if (!minutes || minutes <= 0) {
|
||||||
return ''
|
return ''
|
||||||
|
|||||||
@@ -123,12 +123,20 @@
|
|||||||
<StatsOverview />
|
<StatsOverview />
|
||||||
|
|
||||||
<!-- Token 分布和限制配置 -->
|
<!-- Token 分布和限制配置 -->
|
||||||
<div class="mb-6 grid grid-cols-1 gap-4 md:mb-8 md:gap-6 lg:grid-cols-2">
|
<div
|
||||||
<TokenDistribution />
|
class="mb-6 grid grid-cols-1 gap-4 md:mb-8 md:gap-6 lg:auto-rows-max lg:grid-cols-2 lg:items-start"
|
||||||
<!-- 单key模式下显示限制配置 -->
|
>
|
||||||
<LimitConfig v-if="!multiKeyMode" />
|
<div class="lg:col-span-1">
|
||||||
<!-- 多key模式下显示聚合统计卡片,填充右侧空白 -->
|
<TokenDistribution />
|
||||||
<AggregatedStatsCard v-if="multiKeyMode" />
|
</div>
|
||||||
|
<!-- 单key模式下显示限制配置,使用整行避免出现大片空白 -->
|
||||||
|
<div v-if="!multiKeyMode" class="lg:col-span-2">
|
||||||
|
<LimitConfig />
|
||||||
|
</div>
|
||||||
|
<!-- 多key模式下显示聚合统计卡片,与 TokenDistribution 并排 -->
|
||||||
|
<div v-if="multiKeyMode" class="lg:col-span-1">
|
||||||
|
<AggregatedStatsCard />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 模型使用统计 -->
|
<!-- 模型使用统计 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user