feat: 给key增加总用量限制

This commit is contained in:
itzhan
2025-09-19 21:57:24 +08:00
parent 4d78471891
commit ec28b66e7f
11 changed files with 604 additions and 8 deletions

View File

@@ -218,6 +218,20 @@
/>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">
总费用限制 (美元)
</label>
<input
v-model="form.totalUsageLimit"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200"
min="0"
placeholder="不修改 (0 表示无限制)"
step="0.01"
type="number"
/>
</div>
<!-- Opus 模型周费用限制 -->
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">
@@ -521,6 +535,7 @@ const form = reactive({
rateLimitRequests: '',
concurrencyLimit: '',
dailyCostLimit: '',
totalUsageLimit: '',
weeklyOpusCostLimit: '', // 新增Opus周费用限制
permissions: '', // 空字符串表示不修改
claudeAccountId: '',
@@ -652,6 +667,9 @@ const batchUpdateApiKeys = async () => {
if (form.dailyCostLimit !== '' && form.dailyCostLimit !== null) {
updates.dailyCostLimit = parseFloat(form.dailyCostLimit)
}
if (form.totalUsageLimit !== '' && form.totalUsageLimit !== null) {
updates.totalUsageLimit = parseFloat(form.totalUsageLimit)
}
if (form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null) {
updates.weeklyOpusCostLimit = parseFloat(form.weeklyOpusCostLimit)
}

View File

@@ -329,12 +329,61 @@
step="0.01"
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="dark:text灰-400 text-xs text-gray-500">
设置此 API Key 每日的费用限制超过限制将拒绝请求0 或留空表示无限制
</p>
</div>
</div>
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>总费用限制 (美元)</label
>
<div class="space-y-2">
<div class="flex gap-2">
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = '100'"
>
$100
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = '500'"
>
$500
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = '1000'"
>
$1000
</button>
<button
class="rounded bg-gray-100 px-2 py-1 text-xs font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = ''"
>
自定义
</button>
</div>
<input
v-model="form.totalUsageLimit"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
min="0"
placeholder="0 表示无限制"
step="0.01"
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
设置此 API Key 的累计总费用限制达到限制后将拒绝所有后续请求0 或留空表示无限制
</p>
</div>
</div>
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>Opus 模型周费用限制 (美元)</label
@@ -861,6 +910,7 @@ const form = reactive({
rateLimitCost: '', // 新增:费用限制
concurrencyLimit: '',
dailyCostLimit: '',
totalUsageLimit: '',
weeklyOpusCostLimit: '',
expireDuration: '',
customExpireDate: '',
@@ -1199,6 +1249,10 @@ const createApiKey = async () => {
form.dailyCostLimit !== '' && form.dailyCostLimit !== null
? parseFloat(form.dailyCostLimit)
: 0,
totalUsageLimit:
form.totalUsageLimit !== '' && form.totalUsageLimit !== null
? parseFloat(form.totalUsageLimit)
: 0,
weeklyOpusCostLimit:
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
? parseFloat(form.weeklyOpusCostLimit)

View File

@@ -273,6 +273,55 @@
</div>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>总费用限制 (美元)</label
>
<div class="space-y-3">
<div class="flex gap-2">
<button
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = '100'"
>
$100
</button>
<button
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = '500'"
>
$500
</button>
<button
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = '1000'"
>
$1000
</button>
<button
class="rounded-lg bg-gray-100 px-3 py-1 text-sm font-medium hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
type="button"
@click="form.totalUsageLimit = ''"
>
自定义
</button>
</div>
<input
v-model="form.totalUsageLimit"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
min="0"
placeholder="0 表示无限制"
step="0.01"
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
设置此 API Key 的累计总费用限制达到限制后将拒绝所有后续请求0 或留空表示无限制
</p>
</div>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>Opus 模型周费用限制 (美元)</label
@@ -713,6 +762,7 @@ const form = reactive({
rateLimitCost: '', // 新增:费用限制
concurrencyLimit: '',
dailyCostLimit: '',
totalUsageLimit: '',
weeklyOpusCostLimit: '',
permissions: 'all',
claudeAccountId: '',
@@ -826,6 +876,10 @@ const updateApiKey = async () => {
form.dailyCostLimit !== '' && form.dailyCostLimit !== null
? parseFloat(form.dailyCostLimit)
: 0,
totalUsageLimit:
form.totalUsageLimit !== '' && form.totalUsageLimit !== null
? parseFloat(form.totalUsageLimit)
: 0,
weeklyOpusCostLimit:
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
? parseFloat(form.weeklyOpusCostLimit)
@@ -1100,6 +1154,7 @@ onMounted(async () => {
form.rateLimitRequests = props.apiKey.rateLimitRequests || ''
form.concurrencyLimit = props.apiKey.concurrencyLimit || ''
form.dailyCostLimit = props.apiKey.dailyCostLimit || ''
form.totalUsageLimit = props.apiKey.totalUsageLimit || ''
form.weeklyOpusCostLimit = props.apiKey.weeklyOpusCostLimit || ''
form.permissions = props.apiKey.permissions || 'all'
// 处理 Claude 账号(区分 OAuth 和 Console

View File

@@ -190,6 +190,31 @@
</span>
</div>
<div v-if="apiKey.totalUsageLimit > 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.totalUsageLimit.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">
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
<i class="fas fa-clock mr-1 text-blue-500" />
@@ -250,6 +275,7 @@ const totalTokens = computed(() => props.apiKey.usage?.total?.tokens || 0)
const dailyTokens = computed(() => props.apiKey.usage?.daily?.tokens || 0)
const totalCost = computed(() => props.apiKey.usage?.total?.cost || 0)
const dailyCost = computed(() => props.apiKey.dailyCost || 0)
const totalUsageLimit = computed(() => props.apiKey.totalUsageLimit || 0)
const inputTokens = computed(() => props.apiKey.usage?.total?.inputTokens || 0)
const outputTokens = computed(() => props.apiKey.usage?.total?.outputTokens || 0)
const cacheCreateTokens = computed(() => props.apiKey.usage?.total?.cacheCreateTokens || 0)
@@ -260,6 +286,7 @@ const tpm = computed(() => props.apiKey.usage?.averages?.tpm || 0)
const hasLimits = computed(() => {
return (
props.apiKey.dailyCostLimit > 0 ||
props.apiKey.totalUsageLimit > 0 ||
props.apiKey.concurrencyLimit > 0 ||
props.apiKey.rateLimitWindow > 0 ||
props.apiKey.tokenLimit > 0
@@ -271,6 +298,11 @@ const dailyCostPercentage = computed(() => {
return (dailyCost.value / props.apiKey.dailyCostLimit) * 100
})
const totalUsagePercentage = computed(() => {
if (!totalUsageLimit.value || totalUsageLimit.value === 0) return 0
return (totalCost.value / totalUsageLimit.value) * 100
})
// 方法
const formatNumber = (num) => {
if (!num && num !== 0) return '0'