mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
fix: api-keys页面布局优化
This commit is contained in:
94
web/admin-spa/src/components/apikeys/LimitBadge.vue
Normal file
94
web/admin-spa/src/components/apikeys/LimitBadge.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div class="inline-flex items-center gap-1.5 rounded-md px-2 py-1" :class="badgeClass">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i :class="['text-xs', iconClass]" />
|
||||||
|
<span class="text-xs font-medium">{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="text-xs font-semibold">${{ current.toFixed(2) }}</span>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">/</span>
|
||||||
|
<span class="text-xs">${{ limit.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- 小型进度条 -->
|
||||||
|
<div class="h-1 w-12 rounded-full bg-gray-200 dark:bg-gray-600">
|
||||||
|
<div
|
||||||
|
class="h-1 rounded-full transition-all duration-300"
|
||||||
|
:class="progressClass"
|
||||||
|
:style="{ width: progress + '%' }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
validator: (value) => ['daily', 'opus', 'window'].includes(value)
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const progress = computed(() => {
|
||||||
|
if (!props.limit || props.limit === 0) return 0
|
||||||
|
const percentage = (props.current / props.limit) * 100
|
||||||
|
return Math.min(percentage, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
const badgeClass = computed(() => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'bg-gray-50 dark:bg-gray-700/50'
|
||||||
|
case 'opus':
|
||||||
|
return 'bg-indigo-50 dark:bg-indigo-900/20'
|
||||||
|
case 'window':
|
||||||
|
return 'bg-blue-50 dark:bg-blue-900/20'
|
||||||
|
default:
|
||||||
|
return 'bg-gray-50 dark:bg-gray-700/50'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const iconClass = computed(() => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'fas fa-calendar-day text-gray-500'
|
||||||
|
case 'opus':
|
||||||
|
return 'fas fa-gem text-indigo-500'
|
||||||
|
case 'window':
|
||||||
|
return 'fas fa-clock text-blue-500'
|
||||||
|
default:
|
||||||
|
return 'fas fa-info-circle text-gray-500'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const progressClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
if (p >= 100) return 'bg-red-500'
|
||||||
|
if (p >= 80) return 'bg-yellow-500'
|
||||||
|
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'bg-green-500'
|
||||||
|
case 'opus':
|
||||||
|
return 'bg-indigo-500'
|
||||||
|
case 'window':
|
||||||
|
return 'bg-blue-500'
|
||||||
|
default:
|
||||||
|
return 'bg-gray-500'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
213
web/admin-spa/src/components/apikeys/LimitProgressBar.vue
Normal file
213
web/admin-spa/src/components/apikeys/LimitProgressBar.vue
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="relative h-7 w-full overflow-hidden rounded-md" :class="containerClass">
|
||||||
|
<!-- 背景层 -->
|
||||||
|
<div class="absolute inset-0" :class="backgroundClass"></div>
|
||||||
|
|
||||||
|
<!-- 进度条层 -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 h-full transition-all duration-500 ease-out"
|
||||||
|
:class="progressBarClass"
|
||||||
|
:style="{ width: progress + '%' }"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- 文字层 -->
|
||||||
|
<div class="relative z-10 flex h-full items-center justify-between px-2">
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<i :class="['text-xs', iconClass]" />
|
||||||
|
<span class="text-xs font-medium" :class="textClass">{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="text-xs font-bold" :class="valueTextClass"> ${{ current.toFixed(2) }} </span>
|
||||||
|
<span class="text-xs" :class="textClass">/ ${{ limit.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 闪光效果(可选) -->
|
||||||
|
<div
|
||||||
|
v-if="showShine && progress > 0"
|
||||||
|
class="absolute inset-0 opacity-30"
|
||||||
|
:style="{
|
||||||
|
background:
|
||||||
|
'linear-gradient(105deg, transparent 40%, rgba(255,255,255,0.7) 50%, transparent 60%)',
|
||||||
|
animation: 'shine 3s infinite'
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
validator: (value) => ['daily', 'opus', 'window'].includes(value)
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
showShine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const progress = computed(() => {
|
||||||
|
if (!props.limit || props.limit === 0) return 0
|
||||||
|
const percentage = (props.current / props.limit) * 100
|
||||||
|
return Math.min(percentage, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 容器样式
|
||||||
|
const containerClass = computed(() => {
|
||||||
|
return 'bg-gradient-to-r border border-opacity-20'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 背景样式(浅色背景)
|
||||||
|
const backgroundClass = computed(() => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'bg-gradient-to-r from-green-50 to-green-100 dark:from-green-950/30 dark:to-green-900/30'
|
||||||
|
case 'opus':
|
||||||
|
return 'bg-gradient-to-r from-purple-50 to-indigo-100 dark:from-purple-950/30 dark:to-indigo-900/30'
|
||||||
|
case 'window':
|
||||||
|
return 'bg-gradient-to-r from-blue-50 to-cyan-100 dark:from-blue-950/30 dark:to-cyan-900/30'
|
||||||
|
default:
|
||||||
|
return 'bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-700'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 进度条样式(根据进度值动态变化)
|
||||||
|
const progressBarClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
|
||||||
|
if (props.type === 'daily') {
|
||||||
|
if (p >= 90) {
|
||||||
|
return 'bg-gradient-to-r from-red-500 to-red-600'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
return 'bg-gradient-to-r from-yellow-500 to-orange-500'
|
||||||
|
} else {
|
||||||
|
return 'bg-gradient-to-r from-green-500 to-emerald-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.type === 'opus') {
|
||||||
|
if (p >= 90) {
|
||||||
|
return 'bg-gradient-to-r from-red-500 to-pink-600'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
return 'bg-gradient-to-r from-orange-500 to-amber-500'
|
||||||
|
} else {
|
||||||
|
return 'bg-gradient-to-r from-purple-500 to-indigo-600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.type === 'window') {
|
||||||
|
if (p >= 90) {
|
||||||
|
return 'bg-gradient-to-r from-red-500 to-rose-600'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
return 'bg-gradient-to-r from-orange-500 to-yellow-500'
|
||||||
|
} else {
|
||||||
|
return 'bg-gradient-to-r from-blue-500 to-cyan-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'bg-gradient-to-r from-gray-400 to-gray-500'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图标类
|
||||||
|
const iconClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
let colorClass = ''
|
||||||
|
|
||||||
|
if (p >= 90) {
|
||||||
|
colorClass = 'text-red-600 dark:text-red-400'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
colorClass = 'text-orange-600 dark:text-orange-400'
|
||||||
|
} else {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
colorClass = 'text-green-600 dark:text-green-400'
|
||||||
|
break
|
||||||
|
case 'opus':
|
||||||
|
colorClass = 'text-purple-600 dark:text-purple-400'
|
||||||
|
break
|
||||||
|
case 'window':
|
||||||
|
colorClass = 'text-blue-600 dark:text-blue-400'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
colorClass = 'text-gray-500 dark:text-gray-400'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconName = ''
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
iconName = 'fas fa-calendar-day'
|
||||||
|
break
|
||||||
|
case 'opus':
|
||||||
|
iconName = 'fas fa-gem'
|
||||||
|
break
|
||||||
|
case 'window':
|
||||||
|
iconName = 'fas fa-clock'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
iconName = 'fas fa-infinity'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${iconName} ${colorClass}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 文字颜色(根据进度自动调整)
|
||||||
|
const textClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
if (p > 50) {
|
||||||
|
// 进度超过50%时,文字使用白色(在深色进度条上)
|
||||||
|
return 'text-white drop-shadow-sm'
|
||||||
|
} else {
|
||||||
|
// 进度较低时,文字使用深色
|
||||||
|
return 'text-gray-600 dark:text-gray-300'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 数值文字颜色(更突出)
|
||||||
|
const valueTextClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
if (p > 50) {
|
||||||
|
return 'text-white drop-shadow-md'
|
||||||
|
} else {
|
||||||
|
return 'text-gray-800 dark:text-gray-200'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(200%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加柔和的边框 */
|
||||||
|
.border-opacity-20 {
|
||||||
|
border-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .border-opacity-20 {
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
241
web/admin-spa/src/components/apikeys/WindowLimitBar.vue
Normal file
241
web/admin-spa/src/components/apikeys/WindowLimitBar.vue
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full space-y-1">
|
||||||
|
<!-- 时间窗口进度条 -->
|
||||||
|
<div
|
||||||
|
class="relative h-7 w-full overflow-hidden rounded-md border border-opacity-20 bg-gradient-to-r from-blue-50 to-cyan-100 dark:from-blue-950/30 dark:to-cyan-900/30"
|
||||||
|
>
|
||||||
|
<!-- 时间进度条背景 -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 h-full bg-gradient-to-r from-blue-500 to-cyan-500 opacity-20 transition-all duration-1000"
|
||||||
|
:style="{ width: timeProgress + '%' }"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- 文字层 -->
|
||||||
|
<div class="relative z-10 flex h-full items-center justify-between px-2">
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<i class="fas fa-clock text-xs text-blue-600 dark:text-blue-400" />
|
||||||
|
<span class="text-xs font-medium text-gray-700 dark:text-gray-200">
|
||||||
|
{{ rateLimitWindow }}分钟窗口
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="text-xs font-bold"
|
||||||
|
:class="
|
||||||
|
remainingSeconds > 0
|
||||||
|
? 'text-blue-700 dark:text-blue-300'
|
||||||
|
: 'text-gray-400 dark:text-gray-500'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ remainingSeconds > 0 ? formatTime(remainingSeconds) : '未激活' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 费用和请求限制(如果有的话) -->
|
||||||
|
<div v-if="costLimit > 0 || requestLimit > 0" class="flex gap-1">
|
||||||
|
<!-- 费用限制进度条 -->
|
||||||
|
<div
|
||||||
|
v-if="costLimit > 0"
|
||||||
|
class="relative h-6 overflow-hidden rounded-md border border-opacity-20 bg-gradient-to-r from-green-50 to-emerald-100 dark:from-green-950/30 dark:to-emerald-900/30"
|
||||||
|
:class="requestLimit > 0 ? 'w-1/2' : 'w-full'"
|
||||||
|
>
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 h-full transition-all duration-500 ease-out"
|
||||||
|
:class="getCostProgressBarClass()"
|
||||||
|
:style="{ width: costProgress + '%' }"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- 文字 -->
|
||||||
|
<div class="relative z-10 flex h-full items-center justify-between px-2">
|
||||||
|
<span class="text-[10px] font-medium" :class="getCostTextClass()">费用</span>
|
||||||
|
<span class="text-[10px] font-bold" :class="getCostValueTextClass()">
|
||||||
|
${{ currentCost.toFixed(1) }}/${{ costLimit.toFixed(0) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 请求限制进度条 -->
|
||||||
|
<div
|
||||||
|
v-if="requestLimit > 0"
|
||||||
|
class="relative h-6 overflow-hidden rounded-md border border-opacity-20 bg-gradient-to-r from-purple-50 to-indigo-100 dark:from-purple-950/30 dark:to-indigo-900/30"
|
||||||
|
:class="costLimit > 0 ? 'w-1/2' : 'w-full'"
|
||||||
|
>
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 h-full transition-all duration-500 ease-out"
|
||||||
|
:class="getRequestProgressBarClass()"
|
||||||
|
:style="{ width: requestProgress + '%' }"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- 文字 -->
|
||||||
|
<div class="relative z-10 flex h-full items-center justify-between px-2">
|
||||||
|
<span class="text-[10px] font-medium" :class="getRequestTextClass()">请求</span>
|
||||||
|
<span class="text-[10px] font-bold" :class="getRequestValueTextClass()">
|
||||||
|
{{ currentRequests }}/{{ requestLimit }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
rateLimitWindow: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
remainingSeconds: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
currentRequests: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
requestLimit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
currentCost: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
costLimit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
currentTokens: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
tokenLimit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 费用进度
|
||||||
|
const costProgress = computed(() => {
|
||||||
|
if (!props.costLimit || props.costLimit === 0) return 0
|
||||||
|
const percentage = (props.currentCost / props.costLimit) * 100
|
||||||
|
return Math.min(percentage, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求进度
|
||||||
|
const requestProgress = computed(() => {
|
||||||
|
if (!props.requestLimit || props.requestLimit === 0) return 0
|
||||||
|
const percentage = (props.currentRequests / props.requestLimit) * 100
|
||||||
|
return Math.min(percentage, 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 时间进度(倒计时)
|
||||||
|
const timeProgress = computed(() => {
|
||||||
|
if (!props.rateLimitWindow || props.rateLimitWindow === 0) return 0
|
||||||
|
const totalSeconds = props.rateLimitWindow * 60
|
||||||
|
const elapsed = totalSeconds - props.remainingSeconds
|
||||||
|
return Math.max(0, (elapsed / totalSeconds) * 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 费用进度条颜色
|
||||||
|
const getCostProgressBarClass = () => {
|
||||||
|
const p = costProgress.value
|
||||||
|
if (p >= 90) {
|
||||||
|
return 'bg-gradient-to-r from-red-500 to-rose-600'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
return 'bg-gradient-to-r from-orange-500 to-amber-500'
|
||||||
|
} else {
|
||||||
|
return 'bg-gradient-to-r from-green-500 to-emerald-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求进度条颜色
|
||||||
|
const getRequestProgressBarClass = () => {
|
||||||
|
const p = requestProgress.value
|
||||||
|
if (p >= 90) {
|
||||||
|
return 'bg-gradient-to-r from-red-500 to-pink-600'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
return 'bg-gradient-to-r from-orange-500 to-yellow-500'
|
||||||
|
} else {
|
||||||
|
return 'bg-gradient-to-r from-purple-500 to-indigo-600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 费用文字颜色
|
||||||
|
const getCostTextClass = () => {
|
||||||
|
const p = costProgress.value
|
||||||
|
if (p > 50) {
|
||||||
|
return 'text-white drop-shadow-sm'
|
||||||
|
} else {
|
||||||
|
return 'text-gray-600 dark:text-gray-300'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCostValueTextClass = () => {
|
||||||
|
const p = costProgress.value
|
||||||
|
if (p > 50) {
|
||||||
|
return 'text-white drop-shadow-md'
|
||||||
|
} else {
|
||||||
|
return 'text-gray-800 dark:text-gray-200'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求文字颜色
|
||||||
|
const getRequestTextClass = () => {
|
||||||
|
const p = requestProgress.value
|
||||||
|
if (p > 50) {
|
||||||
|
return 'text-white drop-shadow-sm'
|
||||||
|
} else {
|
||||||
|
return 'text-gray-600 dark:text-gray-300'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRequestValueTextClass = () => {
|
||||||
|
const p = requestProgress.value
|
||||||
|
if (p > 50) {
|
||||||
|
return 'text-white drop-shadow-md'
|
||||||
|
} else {
|
||||||
|
return 'text-gray-800 dark:text-gray-200'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (seconds) => {
|
||||||
|
if (seconds === null || seconds === undefined) return '--:--'
|
||||||
|
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h${minutes}m`
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return `${minutes}m${secs}s`
|
||||||
|
} else {
|
||||||
|
return `${secs}s`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化Token数 - 暂时未使用
|
||||||
|
// const formatTokens = (count) => {
|
||||||
|
// if (count >= 1000000) {
|
||||||
|
// return (count / 1000000).toFixed(1) + 'M'
|
||||||
|
// } else if (count >= 1000) {
|
||||||
|
// return (count / 1000).toFixed(1) + 'K'
|
||||||
|
// }
|
||||||
|
// return count.toString()
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.border-opacity-20 {
|
||||||
|
border-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .border-opacity-20 {
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -293,7 +293,7 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[8%] min-w-[60px] cursor-pointer px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
class="w-[4%] min-w-[40px] cursor-pointer px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||||
@click="sortApiKeys('periodCost')"
|
@click="sortApiKeys('periodCost')"
|
||||||
>
|
>
|
||||||
费用
|
费用
|
||||||
@@ -308,7 +308,12 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[8%] min-w-[60px] cursor-pointer px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
class="w-[14%] min-w-[120px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
限制
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="w-[5%] min-w-[45px] cursor-pointer px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||||
@click="sortApiKeys('periodTokens')"
|
@click="sortApiKeys('periodTokens')"
|
||||||
>
|
>
|
||||||
Token
|
Token
|
||||||
@@ -323,7 +328,7 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[8%] min-w-[60px] cursor-pointer px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
class="w-[5%] min-w-[45px] cursor-pointer px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||||
@click="sortApiKeys('periodRequests')"
|
@click="sortApiKeys('periodRequests')"
|
||||||
>
|
>
|
||||||
请求数
|
请求数
|
||||||
@@ -338,7 +343,7 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[9%] min-w-[80px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
class="w-[8%] min-w-[70px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||||
@click="sortApiKeys('lastUsedAt')"
|
@click="sortApiKeys('lastUsedAt')"
|
||||||
>
|
>
|
||||||
最后使用
|
最后使用
|
||||||
@@ -353,7 +358,7 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[9%] min-w-[80px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
class="w-[8%] min-w-[70px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||||
@click="sortApiKeys('createdAt')"
|
@click="sortApiKeys('createdAt')"
|
||||||
>
|
>
|
||||||
创建时间
|
创建时间
|
||||||
@@ -368,7 +373,7 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[9%] min-w-[80px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
class="w-[8%] min-w-[70px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
|
||||||
@click="sortApiKeys('expiresAt')"
|
@click="sortApiKeys('expiresAt')"
|
||||||
>
|
>
|
||||||
过期时间
|
过期时间
|
||||||
@@ -383,7 +388,7 @@
|
|||||||
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
<i v-else class="fas fa-sort ml-1 text-gray-400" />
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="w-[27%] min-w-[180px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
|
class="w-[23%] min-w-[170px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
操作
|
操作
|
||||||
</th>
|
</th>
|
||||||
@@ -530,68 +535,63 @@
|
|||||||
</td>
|
</td>
|
||||||
<!-- 费用 -->
|
<!-- 费用 -->
|
||||||
<td class="whitespace-nowrap px-3 py-1.5 text-right" style="font-size: 13px">
|
<td class="whitespace-nowrap px-3 py-1.5 text-right" style="font-size: 13px">
|
||||||
<div class="space-y-2">
|
<span
|
||||||
<span
|
class="font-semibold text-blue-600 dark:text-blue-400"
|
||||||
class="font-medium text-blue-600 dark:text-blue-400"
|
style="font-size: 14px"
|
||||||
style="font-size: 13px"
|
>
|
||||||
>
|
${{ getPeriodCost(key).toFixed(2) }}
|
||||||
${{ getPeriodCost(key).toFixed(2) }}
|
</span>
|
||||||
</span>
|
</td>
|
||||||
|
<!-- 限制 -->
|
||||||
|
<td class="px-2 py-1.5" style="font-size: 12px">
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
<!-- 每日费用限制进度条 -->
|
<!-- 每日费用限制进度条 -->
|
||||||
<div v-if="key.dailyCostLimit > 0" class="space-y-1">
|
<LimitProgressBar
|
||||||
<div class="flex items-center justify-between text-xs">
|
v-if="key.dailyCostLimit > 0"
|
||||||
<span class="text-gray-500 dark:text-gray-400">每日费用</span>
|
:current="key.dailyCost || 0"
|
||||||
<span class="text-gray-700 dark:text-gray-300">
|
label="每日"
|
||||||
${{ (key.dailyCost || 0).toFixed(2) }} / ${{
|
:limit="key.dailyCostLimit"
|
||||||
key.dailyCostLimit.toFixed(2)
|
type="daily"
|
||||||
}}
|
/>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="h-1.5 w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
|
||||||
<div
|
|
||||||
class="h-1.5 rounded-full transition-all duration-300"
|
|
||||||
:class="getDailyCostProgressColor(key)"
|
|
||||||
:style="{ width: getDailyCostProgress(key) + '%' }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Opus 周费用限制进度条 -->
|
<!-- Opus 周费用限制进度条 -->
|
||||||
<div v-if="key.weeklyOpusCostLimit > 0" class="space-y-1">
|
<LimitProgressBar
|
||||||
<div class="flex items-center justify-between text-xs">
|
v-if="key.weeklyOpusCostLimit > 0"
|
||||||
<span class="text-gray-500 dark:text-gray-400">Opus周费用</span>
|
:current="key.weeklyOpusCost || 0"
|
||||||
<span class="text-gray-700 dark:text-gray-300">
|
label="Opus"
|
||||||
${{ (key.weeklyOpusCost || 0).toFixed(2) }} / ${{
|
:limit="key.weeklyOpusCostLimit"
|
||||||
key.weeklyOpusCostLimit.toFixed(2)
|
type="opus"
|
||||||
}}
|
/>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="h-1.5 w-full rounded-full bg-gray-200">
|
|
||||||
<div
|
|
||||||
class="h-1.5 rounded-full transition-all duration-300"
|
|
||||||
:class="getWeeklyOpusCostProgressColor(key)"
|
|
||||||
:style="{ width: getWeeklyOpusCostProgress(key) + '%' }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 时间窗口限制进度条 -->
|
<!-- 时间窗口限制进度条 -->
|
||||||
<WindowCountdown
|
<WindowLimitBar
|
||||||
v-if="key.rateLimitWindow > 0"
|
v-if="key.rateLimitWindow > 0"
|
||||||
:cost-limit="key.rateLimitCost"
|
:cost-limit="key.rateLimitCost || 0"
|
||||||
:current-cost="key.currentWindowCost"
|
:current-cost="key.currentWindowCost || 0"
|
||||||
:current-requests="key.currentWindowRequests"
|
:current-requests="key.currentWindowRequests || 0"
|
||||||
:current-tokens="key.currentWindowTokens"
|
:current-tokens="key.currentWindowTokens || 0"
|
||||||
:rate-limit-window="key.rateLimitWindow"
|
:rate-limit-window="key.rateLimitWindow"
|
||||||
:request-limit="key.rateLimitRequests"
|
:remaining-seconds="key.windowRemainingSeconds || 0"
|
||||||
:show-progress="true"
|
:request-limit="key.rateLimitRequests || 0"
|
||||||
:show-tooltip="false"
|
:token-limit="key.tokenLimit || 0"
|
||||||
:token-limit="key.tokenLimit"
|
|
||||||
:window-end-time="key.windowEndTime"
|
|
||||||
:window-remaining-seconds="key.windowRemainingSeconds"
|
|
||||||
:window-start-time="key.windowStartTime"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 如果没有任何限制 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
!key.dailyCostLimit &&
|
||||||
|
!key.weeklyOpusCostLimit &&
|
||||||
|
!key.rateLimitWindow
|
||||||
|
"
|
||||||
|
class="dark:to-gray-750 relative h-7 w-full overflow-hidden rounded-md border border-gray-200 bg-gradient-to-r from-gray-50 to-gray-100 dark:border-gray-700 dark:from-gray-800"
|
||||||
|
>
|
||||||
|
<div class="flex h-full items-center justify-center gap-1.5">
|
||||||
|
<i class="fas fa-infinity text-xs text-gray-400 dark:text-gray-500" />
|
||||||
|
<span class="text-xs font-medium text-gray-400 dark:text-gray-500">
|
||||||
|
无限制
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<!-- Token数量 -->
|
<!-- Token数量 -->
|
||||||
@@ -767,7 +767,7 @@
|
|||||||
|
|
||||||
<!-- 模型统计展开区域 -->
|
<!-- 模型统计展开区域 -->
|
||||||
<tr v-if="key && key.id && expandedApiKeys[key.id]">
|
<tr v-if="key && key.id && expandedApiKeys[key.id]">
|
||||||
<td class="bg-gray-50 px-3 py-3 dark:bg-gray-700" colspan="12">
|
<td class="bg-gray-50 px-3 py-3 dark:bg-gray-700" colspan="13">
|
||||||
<div v-if="!apiKeyModelStats[key.id]" class="py-4 text-center">
|
<div v-if="!apiKeyModelStats[key.id]" class="py-4 text-center">
|
||||||
<div class="loading-spinner mx-auto" />
|
<div class="loading-spinner mx-auto" />
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
@@ -1184,42 +1184,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 限制进度 -->
|
<!-- 限制进度条 -->
|
||||||
<div v-if="key.dailyCostLimit > 0" class="space-y-1">
|
<div class="space-y-1.5">
|
||||||
<div class="flex items-center justify-between text-xs">
|
<!-- 每日费用限制 -->
|
||||||
<span class="text-gray-500 dark:text-gray-400">每日费用限额</span>
|
<LimitProgressBar
|
||||||
<span class="text-gray-700 dark:text-gray-300">
|
v-if="key.dailyCostLimit > 0"
|
||||||
${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
|
:current="key.dailyCost || 0"
|
||||||
</span>
|
label="每日"
|
||||||
</div>
|
:limit="key.dailyCostLimit"
|
||||||
<div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
type="daily"
|
||||||
<div
|
/>
|
||||||
class="h-2 rounded-full transition-all duration-300"
|
|
||||||
:class="getDailyCostProgressColor(key)"
|
<!-- Opus 周费用限制 -->
|
||||||
:style="{ width: getDailyCostProgress(key) + '%' }"
|
<LimitProgressBar
|
||||||
/>
|
v-if="key.weeklyOpusCostLimit > 0"
|
||||||
|
:current="key.weeklyOpusCost || 0"
|
||||||
|
label="Opus"
|
||||||
|
:limit="key.weeklyOpusCostLimit"
|
||||||
|
type="opus"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 时间窗口限制 -->
|
||||||
|
<WindowLimitBar
|
||||||
|
v-if="key.rateLimitWindow > 0"
|
||||||
|
:cost-limit="key.rateLimitCost || 0"
|
||||||
|
:current-cost="key.currentWindowCost || 0"
|
||||||
|
:current-requests="key.currentWindowRequests || 0"
|
||||||
|
:current-tokens="key.currentWindowTokens || 0"
|
||||||
|
:rate-limit-window="key.rateLimitWindow"
|
||||||
|
:remaining-seconds="key.windowRemainingSeconds || 0"
|
||||||
|
:request-limit="key.rateLimitRequests || 0"
|
||||||
|
:token-limit="key.tokenLimit || 0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 无限制显示 -->
|
||||||
|
<div
|
||||||
|
v-if="!key.dailyCostLimit && !key.weeklyOpusCostLimit && !key.rateLimitWindow"
|
||||||
|
class="dark:to-gray-750 relative h-7 w-full overflow-hidden rounded-md border border-gray-200 bg-gradient-to-r from-gray-50 to-gray-100 dark:border-gray-700 dark:from-gray-800"
|
||||||
|
>
|
||||||
|
<div class="flex h-full items-center justify-center gap-1.5">
|
||||||
|
<i class="fas fa-infinity text-xs text-gray-400 dark:text-gray-500" />
|
||||||
|
<span class="text-xs font-medium text-gray-400 dark:text-gray-500">
|
||||||
|
无限制
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 移动端时间窗口限制 -->
|
|
||||||
<WindowCountdown
|
|
||||||
v-if="
|
|
||||||
key.rateLimitWindow > 0 &&
|
|
||||||
(key.rateLimitRequests > 0 || key.tokenLimit > 0 || key.rateLimitCost > 0)
|
|
||||||
"
|
|
||||||
:cost-limit="key.rateLimitCost"
|
|
||||||
:current-cost="key.currentWindowCost"
|
|
||||||
:current-requests="key.currentWindowRequests"
|
|
||||||
:current-tokens="key.currentWindowTokens"
|
|
||||||
:rate-limit-window="key.rateLimitWindow"
|
|
||||||
:request-limit="key.rateLimitRequests"
|
|
||||||
:show-progress="true"
|
|
||||||
:show-tooltip="false"
|
|
||||||
:token-limit="key.tokenLimit"
|
|
||||||
:window-end-time="key.windowEndTime"
|
|
||||||
:window-remaining-seconds="key.windowRemainingSeconds"
|
|
||||||
:window-start-time="key.windowStartTime"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 时间信息 -->
|
<!-- 时间信息 -->
|
||||||
@@ -1760,7 +1770,8 @@ import BatchApiKeyModal from '@/components/apikeys/BatchApiKeyModal.vue'
|
|||||||
import BatchEditApiKeyModal from '@/components/apikeys/BatchEditApiKeyModal.vue'
|
import BatchEditApiKeyModal from '@/components/apikeys/BatchEditApiKeyModal.vue'
|
||||||
import ExpiryEditModal from '@/components/apikeys/ExpiryEditModal.vue'
|
import ExpiryEditModal from '@/components/apikeys/ExpiryEditModal.vue'
|
||||||
import UsageDetailModal from '@/components/apikeys/UsageDetailModal.vue'
|
import UsageDetailModal from '@/components/apikeys/UsageDetailModal.vue'
|
||||||
import WindowCountdown from '@/components/apikeys/WindowCountdown.vue'
|
import LimitProgressBar from '@/components/apikeys/LimitProgressBar.vue'
|
||||||
|
import WindowLimitBar from '@/components/apikeys/WindowLimitBar.vue'
|
||||||
import CustomDropdown from '@/components/common/CustomDropdown.vue'
|
import CustomDropdown from '@/components/common/CustomDropdown.vue'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@@ -2058,7 +2069,7 @@ const loadAccounts = async () => {
|
|||||||
accounts.value.openaiGroups = allGroups.filter((g) => g.platform === 'openai')
|
accounts.value.openaiGroups = allGroups.filter((g) => g.platform === 'openai')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载账户列表失败:', error)
|
// console.error('加载账户列表失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3104,9 +3115,9 @@ const clearAllDeletedApiKeys = async () => {
|
|||||||
|
|
||||||
// 如果有失败的,显示详细信息
|
// 如果有失败的,显示详细信息
|
||||||
if (data.details && data.details.failedCount > 0) {
|
if (data.details && data.details.failedCount > 0) {
|
||||||
const errors = data.details.errors
|
// const errors = data.details.errors
|
||||||
console.error('部分API Keys清空失败:', errors)
|
// console.error('部分API Keys清空失败:', errors)
|
||||||
showToast(`${data.details.failedCount} 个清空失败,请查看控制台`, 'warning')
|
showToast(`${data.details.failedCount} 个清空失败`, 'warning')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新已删除列表
|
// 刷新已删除列表
|
||||||
@@ -3169,7 +3180,7 @@ const batchDeleteApiKeys = async () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('批量删除失败', 'error')
|
showToast('批量删除失败', 'error')
|
||||||
console.error('批量删除 API Keys 失败:', error)
|
// console.error('批量删除 API Keys 失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3276,35 +3287,35 @@ const formatDate = (dateString) => {
|
|||||||
.replace(/\//g, '-')
|
.replace(/\//g, '-')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取每日费用进度
|
// 获取每日费用进度 - 已移到 LimitProgressBar 组件中
|
||||||
const getDailyCostProgress = (key) => {
|
// const getDailyCostProgress = (key) => {
|
||||||
if (!key.dailyCostLimit || key.dailyCostLimit === 0) return 0
|
// if (!key.dailyCostLimit || key.dailyCostLimit === 0) return 0
|
||||||
const percentage = ((key.dailyCost || 0) / key.dailyCostLimit) * 100
|
// const percentage = ((key.dailyCost || 0) / key.dailyCostLimit) * 100
|
||||||
return Math.min(percentage, 100)
|
// return Math.min(percentage, 100)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取每日费用进度条颜色
|
// 获取每日费用进度条颜色 - 已移到 LimitProgressBar 组件中
|
||||||
const getDailyCostProgressColor = (key) => {
|
// const getDailyCostProgressColor = (key) => {
|
||||||
const progress = getDailyCostProgress(key)
|
// const progress = getDailyCostProgress(key)
|
||||||
if (progress >= 100) return 'bg-red-500'
|
// if (progress >= 100) return 'bg-red-500'
|
||||||
if (progress >= 80) return 'bg-yellow-500'
|
// if (progress >= 80) return 'bg-yellow-500'
|
||||||
return 'bg-green-500'
|
// return 'bg-green-500'
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取 Opus 周费用进度
|
// 获取 Opus 周费用进度 - 已移到 LimitBadge 组件中
|
||||||
const getWeeklyOpusCostProgress = (key) => {
|
// const getWeeklyOpusCostProgress = (key) => {
|
||||||
if (!key.weeklyOpusCostLimit || key.weeklyOpusCostLimit === 0) return 0
|
// if (!key.weeklyOpusCostLimit || key.weeklyOpusCostLimit === 0) return 0
|
||||||
const percentage = ((key.weeklyOpusCost || 0) / key.weeklyOpusCostLimit) * 100
|
// const percentage = ((key.weeklyOpusCost || 0) / key.weeklyOpusCostLimit) * 100
|
||||||
return Math.min(percentage, 100)
|
// return Math.min(percentage, 100)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取 Opus 周费用进度条颜色
|
// 获取 Opus 周费用进度条颜色 - 已移到 LimitBadge 组件中
|
||||||
const getWeeklyOpusCostProgressColor = (key) => {
|
// const getWeeklyOpusCostProgressColor = (key) => {
|
||||||
const progress = getWeeklyOpusCostProgress(key)
|
// const progress = getWeeklyOpusCostProgress(key)
|
||||||
if (progress >= 100) return 'bg-red-500'
|
// if (progress >= 100) return 'bg-red-500'
|
||||||
if (progress >= 80) return 'bg-yellow-500'
|
// if (progress >= 80) return 'bg-yellow-500'
|
||||||
return 'bg-green-500'
|
// return 'bg-green-500'
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取总费用进度 - 暂时不用
|
// 获取总费用进度 - 暂时不用
|
||||||
// const getTotalCostProgress = (key) => {
|
// const getTotalCostProgress = (key) => {
|
||||||
@@ -3319,6 +3330,23 @@ const showUsageDetails = (apiKey) => {
|
|||||||
showUsageDetailModal.value = true
|
showUsageDetailModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化时间(秒转换为可读格式) - 已移到 WindowLimitBar 组件中
|
||||||
|
// const formatTime = (seconds) => {
|
||||||
|
// if (seconds === null || seconds === undefined) return '--:--'
|
||||||
|
//
|
||||||
|
// const hours = Math.floor(seconds / 3600)
|
||||||
|
// const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
// const secs = seconds % 60
|
||||||
|
//
|
||||||
|
// if (hours > 0) {
|
||||||
|
// return `${hours}h ${minutes}m`
|
||||||
|
// } else if (minutes > 0) {
|
||||||
|
// return `${minutes}m ${secs}s`
|
||||||
|
// } else {
|
||||||
|
// return `${secs}s`
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// 格式化最后使用时间
|
// 格式化最后使用时间
|
||||||
const formatLastUsed = (dateString) => {
|
const formatLastUsed = (dateString) => {
|
||||||
if (!dateString) return '从未使用'
|
if (!dateString) return '从未使用'
|
||||||
@@ -3531,7 +3559,7 @@ const exportToExcel = () => {
|
|||||||
|
|
||||||
showToast(`成功导出 ${exportData.length} 条API Key用量数据`, 'success')
|
showToast(`成功导出 ${exportData.length} 条API Key用量数据`, 'success')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('导出失败:', error)
|
// console.error('导出失败:', error)
|
||||||
showToast('导出失败,请重试', 'error')
|
showToast('导出失败,请重试', 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user