-
+
{{ getBoundAccountName(key.claudeAccountId) }}
@@ -232,98 +241,100 @@
-
-
-
- 请求数:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.requests) || 0) }}
-
-
-
- Token:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.tokens) || 0) }}
-
-
-
- 费用:
- {{ calculateApiKeyCost(key.usage) }}
-
-
-
- 今日费用:
-
- ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
-
-
-
-
- 并发限制:
- {{ key.concurrencyLimit > 0 ? key.concurrencyLimit : '无限制' }}
-
-
-
- 当前并发:
-
- {{ key.currentConcurrency || 0 }}
- / {{ key.concurrencyLimit }}
-
-
-
-
- 时间窗口:
- {{ key.rateLimitWindow }} 分钟
-
-
-
- 请求限制:
- {{ key.rateLimitRequests }} 次/窗口
-
-
-
- 输入: {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}
- 输出: {{ formatNumber((key.usage && key.usage.total && key.usage.total.outputTokens) || 0) }}
-
-
-
- 缓存创建: {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheCreateTokens) || 0) }}
- 缓存读取: {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheReadTokens) || 0) }}
-
-
-
- RPM: {{ (key.usage && key.usage.averages && key.usage.averages.rpm) || 0 }}
- TPM: {{ (key.usage && key.usage.averages && key.usage.averages.tpm) || 0 }}
-
-
-
-
- 今日: {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.requests) || 0) }}次
- {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.tokens) || 0) }}T
+
+
+
+
+ 今日请求
+ {{ formatNumber((key.usage?.daily?.requests) || 0) }}次
+
+
+ 今日费用
+ ${{ (key.dailyCost || 0).toFixed(4) }}
-
-
-
@@ -373,6 +384,15 @@
|
+
+
+ 模型
+
onApiKeyCustomDateRangeChange(key.id, value)"
class="api-key-date-picker"
:clearable="true"
:unlink-panels="false"
+ @update:model-value="(value) => onApiKeyCustomDateRangeChange(key.id, value)"
/>
@@ -666,25 +686,104 @@
-
-
-
- 使用量
-
-
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.requests) || 0) }} 次
-
-
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.tokens) || 0) }} tokens
-
+
+
+
+
+ 今日使用
+
+ 详情
+
+
+
+
+
+ {{ formatNumber((key.usage?.daily?.requests) || 0) }} 次
+
+
+ 请求
+
+
+
+
+ ${{ (key.dailyCost || 0).toFixed(4) }}
+
+
+ 费用
+
+
+
-
-
- 费用
-
-
- {{ calculateApiKeyCost(key.usage) }}
-
+
+
+
+
+ 每日费用限额
+
+ ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
+
+
+
+
+
+
+
+
+ 窗口限制 ({{ key.rateLimitWindow }}分钟)
+
+
+
+ 请求
+
+
+ {{ key.currentWindowRequests || 0 }}/{{ key.rateLimitRequests }}
+
+
+
+
+ Token
+
+
+ {{ formatTokenCount(key.currentWindowTokens || 0) }}/{{ formatTokenCount(key.tokenLimit) }}
+
+
@@ -720,10 +819,10 @@
-
- {{ expandedKeys.includes(key.id) ? '收起' : '详情' }}
+
+ 查看详情
-
-
-
-
- 详细信息
-
-
-
-
-
- 并发限制:
- {{ key.concurrencyLimit > 0 ? key.concurrencyLimit : '无限制' }}
-
-
- 当前并发:
-
- {{ key.currentConcurrency || 0 }}
- / {{ key.concurrencyLimit }}
-
-
-
- 今日费用:
-
- ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
-
-
-
- 时间窗口:
- {{ key.rateLimitWindow }} 分钟
-
-
- 请求限制:
- {{ key.rateLimitRequests }} 次/窗口
-
-
-
-
-
- 输入 Token:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}
-
-
- 输出 Token:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.outputTokens) || 0) }}
-
-
- 缓存创建:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheCreateTokens) || 0) }}
-
-
- 缓存读取:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheReadTokens) || 0) }}
-
-
-
-
-
-
- 今日请求:
- {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.requests) || 0) }} 次
-
-
- 今日 Token:
- {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.tokens) || 0) }}
-
-
-
-
@@ -959,6 +987,13 @@
@close="closeExpiryEdit"
@save="handleSaveExpiry"
/>
+
+
+
@@ -973,6 +1008,7 @@ import RenewApiKeyModal from '@/components/apikeys/RenewApiKeyModal.vue'
import NewApiKeyModal from '@/components/apikeys/NewApiKeyModal.vue'
import BatchApiKeyModal from '@/components/apikeys/BatchApiKeyModal.vue'
import ExpiryEditModal from '@/components/apikeys/ExpiryEditModal.vue'
+import UsageDetailModal from '@/components/apikeys/UsageDetailModal.vue'
// 响应式数据
const clientsStore = useClientsStore()
@@ -988,13 +1024,13 @@ const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23,
const accounts = ref({ claude: [], gemini: [], claudeGroups: [], geminiGroups: [] })
const editingExpiryKey = ref(null)
const expiryEditModalRef = ref(null)
+const showUsageDetailModal = ref(false)
+const selectedApiKeyForDetail = ref(null)
// 标签相关
const selectedTagFilter = ref('')
const availableTags = ref([])
-// 移动端展开状态
-const expandedKeys = ref([])
// 分页相关
const currentPage = ref(1)
@@ -1183,16 +1219,36 @@ const calculateApiKeyCost = (usage) => {
const getBoundAccountName = (accountId) => {
if (!accountId) return '未知账户'
+ // 检查是否是分组
+ if (accountId.startsWith('group:')) {
+ const groupId = accountId.substring(6) // 移除 'group:' 前缀
+
+ // 从Claude分组中查找
+ const claudeGroup = accounts.value.claudeGroups.find(g => g.id === groupId)
+ if (claudeGroup) {
+ return `分组-${claudeGroup.name}`
+ }
+
+ // 从Gemini分组中查找
+ const geminiGroup = accounts.value.geminiGroups.find(g => g.id === groupId)
+ if (geminiGroup) {
+ return `分组-${geminiGroup.name}`
+ }
+
+ // 如果找不到分组,返回分组ID的前8位
+ return `分组-${groupId.substring(0, 8)}`
+ }
+
// 从Claude账户列表中查找
const claudeAccount = accounts.value.claude.find(acc => acc.id === accountId)
if (claudeAccount) {
- return claudeAccount.name
+ return `账户-${claudeAccount.name}`
}
// 从Gemini账户列表中查找
const geminiAccount = accounts.value.gemini.find(acc => acc.id === accountId)
if (geminiAccount) {
- return geminiAccount.name
+ return `账户-${geminiAccount.name}`
}
// 如果找不到,返回账户ID的前8位
@@ -1548,15 +1604,6 @@ const handleSaveExpiry = async ({ keyId, expiresAt }) => {
}
}
-// 切换移动端卡片展开状态
-const toggleExpanded = (keyId) => {
- const index = expandedKeys.value.indexOf(keyId)
- if (index > -1) {
- expandedKeys.value.splice(index, 1)
- } else {
- expandedKeys.value.push(keyId)
- }
-}
// 格式化日期时间
const formatDate = (dateString) => {
@@ -1571,26 +1618,68 @@ const formatDate = (dateString) => {
}).replace(/\//g, '-')
}
-// 显示API Key详情
-const showApiKey = async (apiKey) => {
- try {
- // 重新获取API Key的完整信息(包含实际的key值)
- const response = await apiClient.get(`/admin/api-keys/${apiKey.id}`)
- if (response.success && response.data) {
- newApiKeyData.value = {
- ...response.data,
- key: response.data.key || response.data.apiKey // 兼容不同的字段名
- }
- showNewApiKeyModal.value = true
- } else {
- showToast('获取API Key信息失败', 'error')
- }
- } catch (error) {
- console.error('Error fetching API key:', error)
- showToast('获取API Key信息失败', 'error')
- }
+// 获取每日费用进度
+const getDailyCostProgress = (key) => {
+ if (!key.dailyCostLimit || key.dailyCostLimit === 0) return 0
+ const percentage = ((key.dailyCost || 0) / key.dailyCostLimit) * 100
+ return Math.min(percentage, 100)
}
+// 获取每日费用进度条颜色
+const getDailyCostProgressColor = (key) => {
+ const progress = getDailyCostProgress(key)
+ if (progress >= 100) return 'bg-red-500'
+ if (progress >= 80) return 'bg-yellow-500'
+ return 'bg-green-500'
+}
+
+// 显示使用详情
+const showUsageDetails = (apiKey) => {
+ selectedApiKeyForDetail.value = apiKey
+ showUsageDetailModal.value = true
+}
+
+// 格式化Token数量(使用K/M单位)
+const formatTokenCount = (count) => {
+ if (count >= 1000000) {
+ return (count / 1000000).toFixed(1) + 'M'
+ } else if (count >= 1000) {
+ return (count / 1000).toFixed(1) + 'K'
+ }
+ return count.toString()
+}
+
+// 获取窗口请求进度
+const getWindowRequestProgress = (key) => {
+ if (!key.rateLimitRequests || key.rateLimitRequests === 0) return 0
+ const percentage = ((key.currentWindowRequests || 0) / key.rateLimitRequests) * 100
+ return Math.min(percentage, 100)
+}
+
+// 获取窗口请求进度条颜色
+const getWindowRequestProgressColor = (key) => {
+ const progress = getWindowRequestProgress(key)
+ if (progress >= 100) return 'bg-red-500'
+ if (progress >= 80) return 'bg-yellow-500'
+ return 'bg-blue-500'
+}
+
+// 获取窗口Token进度
+const getWindowTokenProgress = (key) => {
+ if (!key.tokenLimit || key.tokenLimit === 0) return 0
+ const percentage = ((key.currentWindowTokens || 0) / key.tokenLimit) * 100
+ return Math.min(percentage, 100)
+}
+
+// 获取窗口Token进度条颜色
+const getWindowTokenProgressColor = (key) => {
+ const progress = getWindowTokenProgress(key)
+ if (progress >= 100) return 'bg-red-500'
+ if (progress >= 80) return 'bg-yellow-500'
+ return 'bg-purple-500'
+}
+
+
// 监听筛选条件变化,重置页码
watch([selectedTagFilter, apiKeyStatsTimeRange], () => {
currentPage.value = 1
diff --git a/web/admin-spa/src/views/SettingsView.vue b/web/admin-spa/src/views/SettingsView.vue
index 375fed92..219792cd 100644
--- a/web/admin-spa/src/views/SettingsView.vue
+++ b/web/admin-spa/src/views/SettingsView.vue
@@ -180,8 +180,12 @@
- 网站名称
- 品牌标识
+
+ 网站名称
+
+
+ 品牌标识
+
@@ -205,8 +209,12 @@
- 网站图标
- Favicon
+
+ 网站图标
+
+
+ Favicon
+
|