mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
style: 优化apikeys进度条显示
This commit is contained in:
1
web/admin-spa/package-lock.json
generated
1
web/admin-spa/package-lock.json
generated
@@ -3792,6 +3792,7 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
|
"resolved": "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
|
||||||
"integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
|
"integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.21.3"
|
"node": ">=14.21.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="relative h-8 w-full overflow-hidden rounded-lg shadow-sm" :class="containerClass">
|
<!-- 检查是否为无限制状态 -->
|
||||||
|
<div
|
||||||
|
v-if="!limit || limit <= 0"
|
||||||
|
class="flex items-center justify-center rounded-lg px-3 py-2 text-xs"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-1.5 text-gray-600 dark:text-gray-300">
|
||||||
|
<i class="fas fa-infinity text-sm text-gray-500 dark:text-gray-400" />
|
||||||
|
<span class="font-medium">无限制</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isCompact" class="space-y-1.5">
|
||||||
|
<!-- 使用额度和限额显示在进度条上方右对齐 -->
|
||||||
|
<div class="flex items-center justify-between text-[11px] font-medium">
|
||||||
|
<div class="flex items-center gap-1.5" :class="compactLabelClass">
|
||||||
|
<i :class="['text-[11px]', iconClass]" />
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-gray-700 dark:text-gray-200"
|
||||||
|
>${{ current.toFixed(2) }} / ${{ limit.toFixed(2) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="relative h-1.5 overflow-hidden rounded-full bg-gray-200/85 dark:bg-gray-700/70">
|
||||||
|
<div
|
||||||
|
class="absolute inset-y-0 rounded-full transition-all duration-500 ease-out"
|
||||||
|
:class="compactBarClass"
|
||||||
|
:style="{ width: progress + '%' }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="group relative h-9 w-full overflow-hidden rounded-xl border transition-all duration-300 ease-out"
|
||||||
|
:class="containerClass"
|
||||||
|
>
|
||||||
<!-- 背景层 -->
|
<!-- 背景层 -->
|
||||||
<div class="absolute inset-0" :class="backgroundClass"></div>
|
<div class="absolute inset-0" :class="backgroundClass"></div>
|
||||||
|
|
||||||
@@ -11,6 +44,11 @@
|
|||||||
:style="{ width: progress + '%' }"
|
:style="{ width: progress + '%' }"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<!-- 内部高光边框 -->
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 rounded-xl border border-white/50 opacity-40 mix-blend-overlay dark:border-white/10"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- 文字层 - 使用双层文字技术确保可读性 -->
|
<!-- 文字层 - 使用双层文字技术确保可读性 -->
|
||||||
<div class="relative z-10 flex h-full items-center justify-between px-3">
|
<div class="relative z-10 flex h-full items-center justify-between px-3">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
@@ -19,10 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<span class="text-xs font-bold tabular-nums" :class="currentValueClass">
|
<span class="text-xs font-bold tabular-nums" :class="currentValueClass">
|
||||||
${{ current.toFixed(2) }}
|
${{ current.toFixed(2) }} / ${{ limit.toFixed(2) }}
|
||||||
</span>
|
|
||||||
<span class="text-xs font-medium" :class="limitTextClass">
|
|
||||||
/ ${{ limit.toFixed(2) }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,11 +65,11 @@
|
|||||||
<!-- 闪光效果(可选) -->
|
<!-- 闪光效果(可选) -->
|
||||||
<div
|
<div
|
||||||
v-if="showShine && progress > 0"
|
v-if="showShine && progress > 0"
|
||||||
class="absolute inset-0 opacity-20"
|
class="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-700 group-hover:opacity-30"
|
||||||
:style="{
|
:style="{
|
||||||
background:
|
background:
|
||||||
'linear-gradient(105deg, transparent 40%, rgba(255,255,255,0.5) 50%, transparent 60%)',
|
'linear-gradient(105deg, transparent 35%, rgba(255,255,255,0.55) 48%, transparent 63%)',
|
||||||
animation: 'shine 3s infinite'
|
animation: 'shine 2.8s infinite'
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,7 +83,12 @@ const props = defineProps({
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
validator: (value) => ['daily', 'opus', 'window'].includes(value)
|
validator: (value) => ['daily', 'opus', 'window', 'total'].includes(value)
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'full',
|
||||||
|
validator: (value) => ['full', 'compact'].includes(value)
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -68,28 +108,46 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isCompact = computed(() => props.variant === 'compact')
|
||||||
const progress = computed(() => {
|
const progress = computed(() => {
|
||||||
if (!props.limit || props.limit === 0) return 0
|
// 无限制时不显示进度条
|
||||||
|
if (!props.limit || props.limit <= 0) return 0
|
||||||
const percentage = (props.current / props.limit) * 100
|
const percentage = (props.current / props.limit) * 100
|
||||||
return Math.min(percentage, 100)
|
return Math.min(percentage, 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 容器样式 - 使用更柔和的边框和阴影
|
// 移除百分比显示
|
||||||
|
// const compactPercentage = computed(() => `${Math.min(progress.value, 100).toFixed(0)}%`)
|
||||||
|
|
||||||
|
// 容器样式 - 使用柔和的渐变边框与阴影
|
||||||
const containerClass = computed(() => {
|
const containerClass = computed(() => {
|
||||||
return 'border border-gray-200/80 dark:border-gray-600/50 shadow-sm'
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'border-emerald-200/80 bg-white/80 shadow-[0_10px_24px_rgba(16,185,129,0.18)] group-hover:shadow-[0_14px_30px_rgba(16,185,129,0.22)] dark:border-emerald-500/40 dark:bg-emerald-950/40 dark:shadow-[0_12px_28px_rgba(0,0,0,0.45)]'
|
||||||
|
case 'opus':
|
||||||
|
return 'border-violet-200/80 bg-white/80 shadow-[0_10px_24px_rgba(139,92,246,0.18)] group-hover:shadow-[0_14px_30px_rgba(139,92,246,0.22)] dark:border-violet-500/40 dark:bg-violet-950/40 dark:shadow-[0_12px_28px_rgba(0,0,0,0.45)]'
|
||||||
|
case 'window':
|
||||||
|
return 'border-sky-200/80 bg-white/80 shadow-[0_10px_24px_rgba(56,189,248,0.18)] group-hover:shadow-[0_14px_30px_rgba(56,189,248,0.22)] dark:border-sky-500/40 dark:bg-sky-950/40 dark:shadow-[0_12px_28px_rgba(0,0,0,0.45)]'
|
||||||
|
case 'total':
|
||||||
|
return 'border-blue-200/80 bg-white/80 shadow-[0_10px_24px_rgba(59,130,246,0.18)] group-hover:shadow-[0_14px_30px_rgba(59,130,246,0.22)] dark:border-blue-500/40 dark:bg-blue-950/40 dark:shadow-[0_12px_28px_rgba(0,0,0,0.45)]'
|
||||||
|
default:
|
||||||
|
return 'border-gray-200/80 bg-white/80 shadow-[0_10px_24px_rgba(148,163,184,0.18)] group-hover:shadow-[0_14px_30px_rgba(148,163,184,0.22)] dark:border-gray-600/50 dark:bg-gray-900/50 dark:shadow-[0_12px_28px_rgba(0,0,0,0.45)]'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 背景样式 - 使用更浅的背景色提升对比度
|
// 背景样式 - 使用柔和渐变增强层次
|
||||||
const backgroundClass = computed(() => {
|
const backgroundClass = computed(() => {
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case 'daily':
|
case 'daily':
|
||||||
return 'bg-gray-100/50 dark:bg-gray-800/30'
|
return 'bg-gradient-to-r from-emerald-50 via-green-50 to-emerald-100 dark:from-emerald-900/40 dark:via-emerald-900/20 dark:to-emerald-800/30'
|
||||||
case 'opus':
|
case 'opus':
|
||||||
return 'bg-violet-50/50 dark:bg-violet-950/20'
|
return 'bg-gradient-to-r from-violet-50 via-violet-100 to-fuchsia-100 dark:from-violet-900/40 dark:via-violet-900/20 dark:to-fuchsia-900/30'
|
||||||
case 'window':
|
case 'window':
|
||||||
return 'bg-sky-50/50 dark:bg-sky-950/20'
|
return 'bg-gradient-to-r from-sky-50 via-sky-100 to-cyan-100 dark:from-sky-900/40 dark:via-sky-900/20 dark:to-cyan-900/30'
|
||||||
|
case 'total':
|
||||||
|
return 'bg-gradient-to-r from-blue-50 via-blue-100 to-sky-100 dark:from-blue-900/40 dark:via-blue-900/20 dark:to-sky-900/30'
|
||||||
default:
|
default:
|
||||||
return 'bg-gray-100/50 dark:bg-gray-800/30'
|
return 'bg-gradient-to-r from-gray-100 via-gray-50 to-gray-200 dark:from-gray-900/30 dark:via-gray-900/10 dark:to-gray-800/30'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -99,37 +157,95 @@ const progressBarClass = computed(() => {
|
|||||||
|
|
||||||
if (props.type === 'daily') {
|
if (props.type === 'daily') {
|
||||||
if (p >= 90) {
|
if (p >= 90) {
|
||||||
return 'bg-red-400 dark:bg-red-500'
|
return 'bg-gradient-to-r from-rose-500 via-red-500 to-rose-600 dark:from-rose-500 dark:via-red-500 dark:to-rose-600'
|
||||||
} else if (p >= 70) {
|
} else if (p >= 70) {
|
||||||
return 'bg-amber-400 dark:bg-amber-500'
|
return 'bg-gradient-to-r from-amber-400 via-orange-400 to-amber-500 dark:from-amber-400 dark:via-orange-400 dark:to-amber-500'
|
||||||
} else {
|
} else {
|
||||||
return 'bg-emerald-400 dark:bg-emerald-500'
|
return 'bg-gradient-to-r from-emerald-400 via-emerald-500 to-teal-500 dark:from-emerald-400 dark:via-emerald-500 dark:to-teal-500'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.type === 'opus') {
|
if (props.type === 'opus') {
|
||||||
if (p >= 90) {
|
if (p >= 90) {
|
||||||
return 'bg-red-400 dark:bg-red-500'
|
return 'bg-gradient-to-r from-rose-500 via-red-500 to-rose-600 dark:from-rose-500 dark:via-red-500 dark:to-rose-600'
|
||||||
} else if (p >= 70) {
|
} else if (p >= 70) {
|
||||||
return 'bg-amber-400 dark:bg-amber-500'
|
return 'bg-gradient-to-r from-amber-400 via-orange-400 to-amber-500 dark:from-amber-400 dark:via-orange-400 dark:to-amber-500'
|
||||||
} else {
|
} else {
|
||||||
return 'bg-violet-400 dark:bg-violet-500'
|
return 'bg-gradient-to-r from-violet-400 via-purple-500 to-fuchsia-500 dark:from-violet-400 dark:via-purple-500 dark:to-fuchsia-500'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.type === 'window') {
|
if (props.type === 'window') {
|
||||||
if (p >= 90) {
|
if (p >= 90) {
|
||||||
return 'bg-red-400 dark:bg-red-500'
|
return 'bg-gradient-to-r from-rose-500 via-red-500 to-rose-600 dark:from-rose-500 dark:via-red-500 dark:to-rose-600'
|
||||||
} else if (p >= 70) {
|
} else if (p >= 70) {
|
||||||
return 'bg-amber-400 dark:bg-amber-500'
|
return 'bg-gradient-to-r from-amber-400 via-orange-400 to-amber-500 dark:from-amber-400 dark:via-orange-400 dark:to-amber-500'
|
||||||
} else {
|
} else {
|
||||||
return 'bg-sky-400 dark:bg-sky-500'
|
return 'bg-gradient-to-r from-sky-400 via-cyan-400 to-blue-500 dark:from-sky-400 dark:via-cyan-400 dark:to-blue-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.type === 'total') {
|
||||||
|
if (p >= 90) {
|
||||||
|
return 'bg-gradient-to-r from-rose-500 via-red-500 to-rose-600 dark:from-rose-500 dark:via-red-500 dark:to-rose-600'
|
||||||
|
} else if (p >= 70) {
|
||||||
|
return 'bg-gradient-to-r from-amber-400 via-orange-400 to-amber-500 dark:from-amber-400 dark:via-orange-400 dark:to-amber-500'
|
||||||
|
} else {
|
||||||
|
return 'bg-gradient-to-r from-sky-500 via-blue-500 to-cyan-500 dark:from-sky-500 dark:via-blue-500 dark:to-cyan-500'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'bg-gray-300 dark:bg-gray-400'
|
return 'bg-gray-300 dark:bg-gray-400'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const compactBarClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
|
||||||
|
if (p >= 95) {
|
||||||
|
return 'bg-rose-500 dark:bg-rose-400'
|
||||||
|
}
|
||||||
|
if (p >= 80) {
|
||||||
|
return 'bg-amber-400 dark:bg-amber-300'
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'bg-emerald-500 dark:bg-emerald-400'
|
||||||
|
case 'opus':
|
||||||
|
return 'bg-violet-500 dark:bg-violet-400'
|
||||||
|
case 'window':
|
||||||
|
return 'bg-sky-500 dark:bg-sky-400'
|
||||||
|
case 'total':
|
||||||
|
return 'bg-blue-500 dark:bg-blue-400'
|
||||||
|
default:
|
||||||
|
return 'bg-slate-400 dark:bg-slate-500'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const compactLabelClass = computed(() => {
|
||||||
|
const p = progress.value
|
||||||
|
|
||||||
|
if (p >= 95) {
|
||||||
|
return 'text-rose-600 dark:text-rose-300'
|
||||||
|
}
|
||||||
|
if (p >= 80) {
|
||||||
|
return 'text-amber-600 dark:text-amber-300'
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props.type) {
|
||||||
|
case 'daily':
|
||||||
|
return 'text-emerald-600 dark:text-emerald-300'
|
||||||
|
case 'opus':
|
||||||
|
return 'text-violet-600 dark:text-violet-300'
|
||||||
|
case 'window':
|
||||||
|
return 'text-sky-600 dark:text-sky-300'
|
||||||
|
case 'total':
|
||||||
|
return 'text-blue-600 dark:text-blue-300'
|
||||||
|
default:
|
||||||
|
return 'text-gray-600 dark:text-gray-300'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 图标类
|
// 图标类
|
||||||
const iconClass = computed(() => {
|
const iconClass = computed(() => {
|
||||||
const p = progress.value
|
const p = progress.value
|
||||||
@@ -167,6 +283,9 @@ const iconClass = computed(() => {
|
|||||||
case 'window':
|
case 'window':
|
||||||
iconName = 'fas fa-clock'
|
iconName = 'fas fa-clock'
|
||||||
break
|
break
|
||||||
|
case 'total':
|
||||||
|
iconName = 'fas fa-wallet'
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
iconName = 'fas fa-infinity'
|
iconName = 'fas fa-infinity'
|
||||||
}
|
}
|
||||||
@@ -191,6 +310,8 @@ const labelTextClass = computed(() => {
|
|||||||
return 'text-purple-900 dark:text-purple-100'
|
return 'text-purple-900 dark:text-purple-100'
|
||||||
case 'window':
|
case 'window':
|
||||||
return 'text-blue-900 dark:text-blue-100'
|
return 'text-blue-900 dark:text-blue-100'
|
||||||
|
case 'total':
|
||||||
|
return 'text-blue-900 dark:text-blue-100'
|
||||||
default:
|
default:
|
||||||
return 'text-gray-900 dark:text-gray-100'
|
return 'text-gray-900 dark:text-gray-100'
|
||||||
}
|
}
|
||||||
@@ -219,26 +340,14 @@ const currentValueClass = computed(() => {
|
|||||||
return 'text-purple-800 dark:text-purple-200'
|
return 'text-purple-800 dark:text-purple-200'
|
||||||
case 'window':
|
case 'window':
|
||||||
return 'text-blue-800 dark:text-blue-200'
|
return 'text-blue-800 dark:text-blue-200'
|
||||||
|
case 'total':
|
||||||
|
return 'text-blue-800 dark:text-blue-200'
|
||||||
default:
|
default:
|
||||||
return 'text-gray-900 dark:text-gray-100'
|
return 'text-gray-900 dark:text-gray-100'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 限制值文字颜色
|
|
||||||
const limitTextClass = computed(() => {
|
|
||||||
const p = progress.value
|
|
||||||
|
|
||||||
// 判断限制值是否在进度条上
|
|
||||||
if (p > 85) {
|
|
||||||
// 在进度条上
|
|
||||||
return 'text-white/90 drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)]'
|
|
||||||
} else {
|
|
||||||
// 在背景上
|
|
||||||
return 'text-gray-600 dark:text-gray-400'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -155,66 +155,55 @@
|
|||||||
限制设置
|
限制设置
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-3 rounded-lg bg-gray-50 p-4 dark:bg-gray-700/50">
|
<div class="space-y-3 rounded-lg bg-gray-50 p-4 dark:bg-gray-700/50">
|
||||||
<div v-if="apiKey.dailyCostLimit > 0" class="space-y-2">
|
<div v-if="apiKey.dailyCostLimit > 0" class="space-y-1.5">
|
||||||
<div class="flex items-center justify-between text-sm">
|
<LimitProgressBar
|
||||||
<span class="text-gray-600 dark:text-gray-400">每日费用限制</span>
|
:current="dailyCost"
|
||||||
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
label="每日费用限制"
|
||||||
${{ apiKey.dailyCostLimit.toFixed(2) }}
|
:limit="apiKey.dailyCostLimit"
|
||||||
</span>
|
:show-shine="true"
|
||||||
</div>
|
type="daily"
|
||||||
<div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-600">
|
|
||||||
<div
|
|
||||||
class="h-2 rounded-full transition-all duration-300"
|
|
||||||
:class="
|
|
||||||
dailyCostPercentage >= 100
|
|
||||||
? 'bg-red-500'
|
|
||||||
: dailyCostPercentage >= 80
|
|
||||||
? 'bg-yellow-500'
|
|
||||||
: 'bg-green-500'
|
|
||||||
"
|
|
||||||
:style="{ width: Math.min(dailyCostPercentage, 100) + '%' }"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
|
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
|
||||||
已使用 {{ dailyCostPercentage.toFixed(1) }}%
|
已使用 {{ Math.min(dailyCostPercentage, 100).toFixed(1) }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="apiKey.weeklyOpusCostLimit > 0" class="space-y-1.5">
|
||||||
|
<LimitProgressBar
|
||||||
|
:current="weeklyOpusCost"
|
||||||
|
label="Opus 周费用限制"
|
||||||
|
:limit="apiKey.weeklyOpusCostLimit"
|
||||||
|
:show-shine="true"
|
||||||
|
type="opus"
|
||||||
|
/>
|
||||||
|
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
已使用 {{ Math.min(opusUsagePercentage, 100).toFixed(1) }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="apiKey.totalCostLimit > 0" class="space-y-1.5">
|
||||||
|
<LimitProgressBar
|
||||||
|
:current="totalCost"
|
||||||
|
label="总费用限制"
|
||||||
|
:limit="apiKey.totalCostLimit"
|
||||||
|
:show-shine="true"
|
||||||
|
type="total"
|
||||||
|
/>
|
||||||
|
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
已使用 {{ Math.min(totalUsagePercentage, 100).toFixed(1) }}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="apiKey.concurrencyLimit > 0"
|
v-if="apiKey.concurrencyLimit > 0"
|
||||||
class="flex items-center justify-between text-sm"
|
class="flex items-center justify-between rounded-lg border border-purple-200/70 bg-white/60 px-3 py-2 text-sm shadow-sm dark:border-purple-500/40 dark:bg-purple-950/20"
|
||||||
>
|
>
|
||||||
<span class="text-gray-600 dark:text-gray-400">并发限制</span>
|
<span class="text-gray-600 dark:text-gray-300">并发限制</span>
|
||||||
<span class="font-semibold text-purple-600">
|
<span class="font-semibold text-purple-600 dark:text-purple-300">
|
||||||
{{ apiKey.currentConcurrency || 0 }} / {{ apiKey.concurrencyLimit }}
|
{{ apiKey.currentConcurrency || 0 }} / {{ apiKey.concurrencyLimit }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="apiKey.totalCostLimit > 0" class="space-y-2">
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
|
||||||
<span class="text-gray-600 dark:text-gray-400">总费用限制</span>
|
|
||||||
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
|
||||||
${{ apiKey.totalCostLimit.toFixed(2) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-600">
|
|
||||||
<div
|
|
||||||
class="h-2 rounded-full transition-all duration-300"
|
|
||||||
:class="
|
|
||||||
totalUsagePercentage >= 100
|
|
||||||
? 'bg-red-500'
|
|
||||||
: totalUsagePercentage >= 80
|
|
||||||
? 'bg-yellow-500'
|
|
||||||
: 'bg-indigo-500'
|
|
||||||
"
|
|
||||||
:style="{ width: Math.min(totalUsagePercentage, 100) + '%' }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
已使用 {{ totalUsagePercentage.toFixed(1) }}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="apiKey.rateLimitWindow > 0" class="space-y-2">
|
<div v-if="apiKey.rateLimitWindow > 0" class="space-y-2">
|
||||||
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
<i class="fas fa-clock mr-1 text-blue-500" />
|
<i class="fas fa-clock mr-1 text-blue-500" />
|
||||||
@@ -253,6 +242,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import LimitProgressBar from './LimitProgressBar.vue'
|
||||||
import WindowCountdown from './WindowCountdown.vue'
|
import WindowCountdown from './WindowCountdown.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -276,6 +266,8 @@ const dailyTokens = computed(() => props.apiKey.usage?.daily?.tokens || 0)
|
|||||||
const totalCost = computed(() => props.apiKey.usage?.total?.cost || 0)
|
const totalCost = computed(() => props.apiKey.usage?.total?.cost || 0)
|
||||||
const dailyCost = computed(() => props.apiKey.dailyCost || 0)
|
const dailyCost = computed(() => props.apiKey.dailyCost || 0)
|
||||||
const totalCostLimit = computed(() => props.apiKey.totalCostLimit || 0)
|
const totalCostLimit = computed(() => props.apiKey.totalCostLimit || 0)
|
||||||
|
const weeklyOpusCost = computed(() => props.apiKey.weeklyOpusCost || 0)
|
||||||
|
const weeklyOpusCostLimit = computed(() => props.apiKey.weeklyOpusCostLimit || 0)
|
||||||
const inputTokens = computed(() => props.apiKey.usage?.total?.inputTokens || 0)
|
const inputTokens = computed(() => props.apiKey.usage?.total?.inputTokens || 0)
|
||||||
const outputTokens = computed(() => props.apiKey.usage?.total?.outputTokens || 0)
|
const outputTokens = computed(() => props.apiKey.usage?.total?.outputTokens || 0)
|
||||||
const cacheCreateTokens = computed(() => props.apiKey.usage?.total?.cacheCreateTokens || 0)
|
const cacheCreateTokens = computed(() => props.apiKey.usage?.total?.cacheCreateTokens || 0)
|
||||||
@@ -288,6 +280,7 @@ const hasLimits = computed(() => {
|
|||||||
props.apiKey.dailyCostLimit > 0 ||
|
props.apiKey.dailyCostLimit > 0 ||
|
||||||
props.apiKey.totalCostLimit > 0 ||
|
props.apiKey.totalCostLimit > 0 ||
|
||||||
props.apiKey.concurrencyLimit > 0 ||
|
props.apiKey.concurrencyLimit > 0 ||
|
||||||
|
props.apiKey.weeklyOpusCostLimit > 0 ||
|
||||||
props.apiKey.rateLimitWindow > 0 ||
|
props.apiKey.rateLimitWindow > 0 ||
|
||||||
props.apiKey.tokenLimit > 0
|
props.apiKey.tokenLimit > 0
|
||||||
)
|
)
|
||||||
@@ -303,6 +296,11 @@ const totalUsagePercentage = computed(() => {
|
|||||||
return (totalCost.value / totalCostLimit.value) * 100
|
return (totalCost.value / totalCostLimit.value) * 100
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const opusUsagePercentage = computed(() => {
|
||||||
|
if (!weeklyOpusCostLimit.value || weeklyOpusCostLimit.value === 0) return 0
|
||||||
|
return (weeklyOpusCost.value / weeklyOpusCostLimit.value) * 100
|
||||||
|
})
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const formatNumber = (num) => {
|
const formatNumber = (num) => {
|
||||||
if (!num && num !== 0) return '0'
|
if (!num && num !== 0) return '0'
|
||||||
|
|||||||
@@ -578,48 +578,29 @@
|
|||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
v-if="key.dailyCostLimit > 0"
|
v-if="key.dailyCostLimit > 0"
|
||||||
:current="key.dailyCost || 0"
|
:current="key.dailyCost || 0"
|
||||||
label="每日"
|
label="每日限制"
|
||||||
:limit="key.dailyCostLimit"
|
:limit="key.dailyCostLimit"
|
||||||
type="daily"
|
type="daily"
|
||||||
|
variant="compact"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Opus 周费用限制进度条 -->
|
<!-- 总费用限制进度条(无每日限制时展示) -->
|
||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
v-if="key.weeklyOpusCostLimit > 0"
|
v-else-if="key.totalCostLimit > 0"
|
||||||
:current="key.weeklyOpusCost || 0"
|
:current="key.usage?.total?.cost || 0"
|
||||||
label="Opus"
|
label="总费用限制"
|
||||||
:limit="key.weeklyOpusCostLimit"
|
:limit="key.totalCostLimit"
|
||||||
type="opus"
|
type="total"
|
||||||
/>
|
variant="compact"
|
||||||
|
|
||||||
<!-- 时间窗口限制进度条 -->
|
|
||||||
<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
|
<div
|
||||||
v-if="
|
v-else
|
||||||
!key.dailyCostLimit &&
|
class="flex items-center justify-center gap-1.5 py-2 text-gray-500 dark:text-gray-400"
|
||||||
!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-base" />
|
||||||
<i class="fas fa-infinity text-xs text-gray-400 dark:text-gray-500" />
|
<span class="text-xs font-medium">无限制</span>
|
||||||
<span class="text-xs font-medium text-gray-400 dark:text-gray-500">
|
|
||||||
无限制
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -1219,44 +1200,29 @@
|
|||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
v-if="key.dailyCostLimit > 0"
|
v-if="key.dailyCostLimit > 0"
|
||||||
:current="key.dailyCost || 0"
|
:current="key.dailyCost || 0"
|
||||||
label="每日"
|
label="每日限制"
|
||||||
:limit="key.dailyCostLimit"
|
:limit="key.dailyCostLimit"
|
||||||
type="daily"
|
type="daily"
|
||||||
|
variant="compact"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Opus 周费用限制 -->
|
<!-- 总费用限制(无每日限制时展示) -->
|
||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
v-if="key.weeklyOpusCostLimit > 0"
|
v-else-if="key.totalCostLimit > 0"
|
||||||
:current="key.weeklyOpusCost || 0"
|
:current="key.usage?.total?.cost || 0"
|
||||||
label="Opus"
|
label="总费用限制"
|
||||||
:limit="key.weeklyOpusCostLimit"
|
:limit="key.totalCostLimit"
|
||||||
type="opus"
|
type="total"
|
||||||
/>
|
variant="compact"
|
||||||
|
|
||||||
<!-- 时间窗口限制 -->
|
|
||||||
<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
|
<div
|
||||||
v-if="!key.dailyCostLimit && !key.weeklyOpusCostLimit && !key.rateLimitWindow"
|
v-else
|
||||||
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"
|
class="flex items-center justify-center gap-1.5 py-2 text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
<div class="flex h-full items-center justify-center gap-1.5">
|
<i class="fas fa-infinity text-base" />
|
||||||
<i class="fas fa-infinity text-xs text-gray-400 dark:text-gray-500" />
|
<span class="text-xs font-medium">无限制</span>
|
||||||
<span class="text-xs font-medium text-gray-400 dark:text-gray-500">
|
|
||||||
无限制
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1800,7 +1766,6 @@ 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 LimitProgressBar from '@/components/apikeys/LimitProgressBar.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'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
|
|||||||
Reference in New Issue
Block a user