mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 完成web/admin-spa/src/components/apikeys的国际化并修复语法错误和警告
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<i class="fas fa-edit text-sm text-white sm:text-base" />
|
||||
</div>
|
||||
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 sm:text-xl">
|
||||
编辑 API Key
|
||||
{{ t('apiKeys.editApiKeyModal.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
@@ -30,20 +30,18 @@
|
||||
<div>
|
||||
<label
|
||||
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
|
||||
>名称</label
|
||||
>{{ t('apiKeys.editApiKeyModal.name') }}</label
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
v-model="form.name"
|
||||
class="form-input flex-1 border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
maxlength="100"
|
||||
placeholder="请输入API Key名称"
|
||||
required
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.name"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
maxlength="100"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.namePlaceholder')"
|
||||
required
|
||||
type="text"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">
|
||||
用于识别此 API Key 的用途
|
||||
{{ t('apiKeys.editApiKeyModal.nameHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +49,7 @@
|
||||
<div>
|
||||
<label
|
||||
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
|
||||
>所有者</label
|
||||
>{{ t('apiKeys.editApiKeyModal.owner') }}</label
|
||||
>
|
||||
<select
|
||||
v-model="form.ownerId"
|
||||
@@ -59,11 +57,13 @@
|
||||
>
|
||||
<option v-for="user in availableUsers" :key="user.id" :value="user.id">
|
||||
{{ user.displayName }} ({{ user.username }})
|
||||
<span v-if="user.role === 'admin'" class="text-gray-500">- 管理员</span>
|
||||
<span v-if="user.role === 'admin'" class="text-gray-500">{{
|
||||
t('apiKeys.editApiKeyModal.adminLabel')
|
||||
}}</span>
|
||||
</option>
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">
|
||||
分配此 API Key 给指定用户或管理员,管理员分配时不受用户 API Key 数量限制
|
||||
{{ t('apiKeys.editApiKeyModal.ownerHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -71,13 +71,13 @@
|
||||
<div>
|
||||
<label
|
||||
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
|
||||
>标签</label
|
||||
>{{ t('apiKeys.editApiKeyModal.tags') }}</label
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<!-- 已选择的标签 -->
|
||||
<div v-if="form.tags.length > 0">
|
||||
<div class="mb-2 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
已选择的标签:
|
||||
{{ t('apiKeys.editApiKeyModal.selectedTags') }}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span
|
||||
@@ -100,7 +100,7 @@
|
||||
<!-- 可选择的已有标签 -->
|
||||
<div v-if="unselectedTags.length > 0">
|
||||
<div class="mb-2 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
点击选择已有标签:
|
||||
{{ t('apiKeys.editApiKeyModal.clickToSelectTags') }}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
@@ -119,13 +119,13 @@
|
||||
<!-- 创建新标签 -->
|
||||
<div>
|
||||
<div class="mb-2 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
创建新标签:
|
||||
{{ t('apiKeys.editApiKeyModal.createNewTag') }}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="newTag"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="输入新标签名称"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.newTagPlaceholder')"
|
||||
type="text"
|
||||
@keypress.enter.prevent="addTag"
|
||||
/>
|
||||
@@ -140,7 +140,7 @@
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
用于标记不同团队或用途,方便筛选管理
|
||||
{{ t('apiKeys.editApiKeyModal.tagsHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,68 +156,76 @@
|
||||
<i class="fas fa-tachometer-alt text-xs text-white" />
|
||||
</div>
|
||||
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-200">
|
||||
速率限制设置 (可选)
|
||||
{{ t('apiKeys.editApiKeyModal.rateLimitTitle') }}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300"
|
||||
>时间窗口 (分钟)</label
|
||||
>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.rateLimitWindow')
|
||||
}}</label>
|
||||
<input
|
||||
v-model="form.rateLimitWindow"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="1"
|
||||
placeholder="无限制"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.noLimit')"
|
||||
type="number"
|
||||
/>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">时间段单位</p>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.editApiKeyModal.rateLimitWindowHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300"
|
||||
>请求次数限制</label
|
||||
>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.rateLimitRequests')
|
||||
}}</label>
|
||||
<input
|
||||
v-model="form.rateLimitRequests"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="1"
|
||||
placeholder="无限制"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.noLimit')"
|
||||
type="number"
|
||||
/>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">窗口内最大请求</p>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.editApiKeyModal.rateLimitRequestsHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300"
|
||||
>费用限制 (美元)</label
|
||||
>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.rateLimitCost')
|
||||
}}</label>
|
||||
<input
|
||||
v-model="form.rateLimitCost"
|
||||
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
min="0"
|
||||
placeholder="无限制"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.noLimit')"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">窗口内最大费用</p>
|
||||
<p class="ml-2 mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.editApiKeyModal.rateLimitCostHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 示例说明 -->
|
||||
<div class="rounded-lg bg-blue-100 p-2 dark:bg-blue-900/30">
|
||||
<h5 class="mb-1 text-xs font-semibold text-blue-800 dark:text-blue-400">
|
||||
💡 使用示例
|
||||
{{ t('apiKeys.editApiKeyModal.usageExamples') }}
|
||||
</h5>
|
||||
<div class="space-y-0.5 text-xs text-blue-700 dark:text-blue-300">
|
||||
<div>
|
||||
<strong>示例1:</strong> 时间窗口=60,请求次数=1000 → 每60分钟最多1000次请求
|
||||
<strong>{{ t('apiKeys.editApiKeyModal.example1') }}</strong>
|
||||
</div>
|
||||
<div><strong>示例2:</strong> 时间窗口=1,费用=0.1 → 每分钟最多$0.1费用</div>
|
||||
<div>
|
||||
<strong>示例3:</strong> 窗口=30,请求=50,费用=5 → 每30分钟50次请求且不超$5费用
|
||||
<strong>{{ t('apiKeys.editApiKeyModal.example2') }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ t('apiKeys.editApiKeyModal.example3') }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -225,9 +233,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>每日费用限制 (美元)</label
|
||||
>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.dailyCostLimit')
|
||||
}}</label>
|
||||
<div class="space-y-3">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@@ -256,27 +264,27 @@
|
||||
type="button"
|
||||
@click="form.dailyCostLimit = ''"
|
||||
>
|
||||
自定义
|
||||
{{ t('apiKeys.editApiKeyModal.custom') }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.dailyCostLimit"
|
||||
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 表示无限制"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.dailyCostLimitPlaceholder')"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
设置此 API Key 每日的费用限制,超过限制将拒绝请求,0 或留空表示无限制
|
||||
{{ t('apiKeys.editApiKeyModal.dailyCostHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>Opus 模型周费用限制 (美元)</label
|
||||
>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.weeklyOpusCostLimit')
|
||||
}}</label>
|
||||
<div class="space-y-3">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@@ -305,36 +313,36 @@
|
||||
type="button"
|
||||
@click="form.weeklyOpusCostLimit = ''"
|
||||
>
|
||||
自定义
|
||||
{{ t('apiKeys.editApiKeyModal.custom') }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model="form.weeklyOpusCostLimit"
|
||||
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 表示无限制"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.weeklyOpusCostLimitPlaceholder')"
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
设置 Opus 模型的周费用限制(周一到周日),仅限 Claude 官方账户,0 或留空表示无限制
|
||||
{{ t('apiKeys.editApiKeyModal.weeklyOpusHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>并发限制</label
|
||||
>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.concurrencyLimit')
|
||||
}}</label>
|
||||
<input
|
||||
v-model="form.concurrencyLimit"
|
||||
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 表示无限制"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.concurrencyLimitPlaceholder')"
|
||||
type="number"
|
||||
/>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
设置此 API Key 可同时处理的最大请求数
|
||||
{{ t('apiKeys.editApiKeyModal.concurrencyHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -351,18 +359,18 @@
|
||||
class="ml-2 cursor-pointer text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
for="editIsActive"
|
||||
>
|
||||
激活账号
|
||||
{{ t('apiKeys.editApiKeyModal.activeStatus') }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="mb-4 text-xs text-gray-500 dark:text-gray-400">
|
||||
取消勾选将禁用此 API Key,暂停所有请求,客户端返回 401 错误
|
||||
{{ t('apiKeys.editApiKeyModal.activeStatusHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>服务权限</label
|
||||
>
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.servicePermissions')
|
||||
}}</label>
|
||||
<div class="flex gap-4">
|
||||
<label class="flex cursor-pointer items-center">
|
||||
<input
|
||||
@@ -371,7 +379,9 @@
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">全部服务</span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.allServices')
|
||||
}}</span>
|
||||
</label>
|
||||
<label class="flex cursor-pointer items-center">
|
||||
<input
|
||||
@@ -380,7 +390,9 @@
|
||||
type="radio"
|
||||
value="claude"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">仅 Claude</span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.claudeOnly')
|
||||
}}</span>
|
||||
</label>
|
||||
<label class="flex cursor-pointer items-center">
|
||||
<input
|
||||
@@ -389,7 +401,9 @@
|
||||
type="radio"
|
||||
value="gemini"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">仅 Gemini</span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.geminiOnly')
|
||||
}}</span>
|
||||
</label>
|
||||
<label class="flex cursor-pointer items-center">
|
||||
<input
|
||||
@@ -398,23 +412,25 @@
|
||||
type="radio"
|
||||
value="openai"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">仅 OpenAI</span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.openaiOnly')
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
控制此 API Key 可以访问哪些服务
|
||||
{{ t('apiKeys.editApiKeyModal.permissionsHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label class="text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>专属账号绑定</label
|
||||
>
|
||||
<label class="text-sm font-semibold text-gray-700 dark:text-gray-300">{{
|
||||
t('apiKeys.editApiKeyModal.accountBinding')
|
||||
}}</label>
|
||||
<button
|
||||
class="flex items-center gap-1 text-sm text-blue-600 transition-colors hover:text-blue-800 disabled:cursor-not-allowed disabled:opacity-50 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
:disabled="accountsLoading"
|
||||
title="刷新账号列表"
|
||||
:title="t('apiKeys.editApiKeyModal.refreshAccounts')"
|
||||
type="button"
|
||||
@click="refreshAccounts"
|
||||
>
|
||||
@@ -425,69 +441,73 @@
|
||||
'text-xs'
|
||||
]"
|
||||
/>
|
||||
<span>{{ accountsLoading ? '刷新中...' : '刷新账号' }}</span>
|
||||
<span>{{
|
||||
accountsLoading
|
||||
? t('apiKeys.editApiKeyModal.refreshing')
|
||||
: t('apiKeys.editApiKeyModal.refreshAccounts')
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>Claude 专属账号</label
|
||||
>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.editApiKeyModal.claudeAccount')
|
||||
}}</label>
|
||||
<AccountSelector
|
||||
v-model="form.claudeAccountId"
|
||||
:accounts="localAccounts.claude"
|
||||
default-option-text="使用共享账号池"
|
||||
:default-option-text="t('apiKeys.editApiKeyModal.useSharedPool')"
|
||||
:disabled="form.permissions === 'gemini' || form.permissions === 'openai'"
|
||||
:groups="localAccounts.claudeGroups"
|
||||
placeholder="请选择Claude账号"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.selectClaudeAccount')"
|
||||
platform="claude"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>Gemini 专属账号</label
|
||||
>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.editApiKeyModal.geminiAccount')
|
||||
}}</label>
|
||||
<AccountSelector
|
||||
v-model="form.geminiAccountId"
|
||||
:accounts="localAccounts.gemini"
|
||||
default-option-text="使用共享账号池"
|
||||
:default-option-text="t('apiKeys.editApiKeyModal.useSharedPool')"
|
||||
:disabled="form.permissions === 'claude' || form.permissions === 'openai'"
|
||||
:groups="localAccounts.geminiGroups"
|
||||
placeholder="请选择Gemini账号"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.selectGeminiAccount')"
|
||||
platform="gemini"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>OpenAI 专属账号</label
|
||||
>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.editApiKeyModal.openaiAccount')
|
||||
}}</label>
|
||||
<AccountSelector
|
||||
v-model="form.openaiAccountId"
|
||||
:accounts="localAccounts.openai"
|
||||
default-option-text="使用共享账号池"
|
||||
:default-option-text="t('apiKeys.editApiKeyModal.useSharedPool')"
|
||||
:disabled="form.permissions === 'claude' || form.permissions === 'gemini'"
|
||||
:groups="localAccounts.openaiGroups"
|
||||
placeholder="请选择OpenAI账号"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.selectOpenaiAccount')"
|
||||
platform="openai"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>Bedrock 专属账号</label
|
||||
>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.editApiKeyModal.bedrockAccount')
|
||||
}}</label>
|
||||
<AccountSelector
|
||||
v-model="form.bedrockAccountId"
|
||||
:accounts="localAccounts.bedrock"
|
||||
default-option-text="使用共享账号池"
|
||||
:default-option-text="t('apiKeys.editApiKeyModal.useSharedPool')"
|
||||
:disabled="form.permissions === 'gemini' || form.permissions === 'openai'"
|
||||
:groups="[]"
|
||||
placeholder="请选择Bedrock账号"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.selectBedrockAccount')"
|
||||
platform="bedrock"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
修改绑定账号将影响此API Key的请求路由
|
||||
{{ t('apiKeys.editApiKeyModal.accountBindingHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -503,15 +523,15 @@
|
||||
class="ml-2 cursor-pointer text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
for="editEnableModelRestriction"
|
||||
>
|
||||
启用模型限制
|
||||
{{ t('apiKeys.editApiKeyModal.enableModelRestriction') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="form.enableModelRestriction" class="space-y-3">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>限制的模型列表</label
|
||||
>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.editApiKeyModal.restrictedModels')
|
||||
}}</label>
|
||||
<div
|
||||
class="mb-3 flex min-h-[32px] flex-wrap gap-2 rounded-lg border border-gray-200 bg-gray-50 p-2 dark:border-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
@@ -533,7 +553,7 @@
|
||||
v-if="form.restrictedModels.length === 0"
|
||||
class="text-sm text-gray-400 dark:text-gray-500"
|
||||
>
|
||||
暂无限制的模型
|
||||
{{ t('apiKeys.editApiKeyModal.noRestrictedModels') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
@@ -552,7 +572,7 @@
|
||||
v-if="availableQuickModels.length === 0"
|
||||
class="text-sm italic text-gray-400 dark:text-gray-500"
|
||||
>
|
||||
所有常用模型已在限制列表中
|
||||
{{ t('apiKeys.editApiKeyModal.allCommonModelsRestricted') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -561,7 +581,7 @@
|
||||
<input
|
||||
v-model="form.modelInput"
|
||||
class="form-input flex-1 border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="输入模型名称,按回车添加"
|
||||
:placeholder="t('apiKeys.editApiKeyModal.addRestrictedModelPlaceholder')"
|
||||
type="text"
|
||||
@keydown.enter.prevent="addRestrictedModel"
|
||||
/>
|
||||
@@ -575,7 +595,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
设置此API Key无法访问的模型,例如:claude-opus-4-20250514
|
||||
{{ t('apiKeys.editApiKeyModal.modelRestrictionHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -594,17 +614,17 @@
|
||||
class="ml-2 cursor-pointer text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
for="editEnableClientRestriction"
|
||||
>
|
||||
启用客户端限制
|
||||
{{ t('apiKeys.editApiKeyModal.enableClientRestriction') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="form.enableClientRestriction" class="space-y-3">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-600 dark:text-gray-400"
|
||||
>允许的客户端</label
|
||||
>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.editApiKeyModal.allowedClients')
|
||||
}}</label>
|
||||
<p class="mb-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
勾选允许使用此API Key的客户端
|
||||
{{ t('apiKeys.editApiKeyModal.clientRestrictionHint') }}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<div v-for="client in supportedClients" :key="client.id" class="flex items-start">
|
||||
@@ -635,7 +655,7 @@
|
||||
type="button"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
取消
|
||||
{{ t('apiKeys.editApiKeyModal.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary flex-1 px-6 py-3 font-semibold"
|
||||
@@ -644,7 +664,9 @@
|
||||
>
|
||||
<div v-if="loading" class="loading-spinner mr-2" />
|
||||
<i v-else class="fas fa-save mr-2" />
|
||||
{{ loading ? '保存中...' : '保存修改' }}
|
||||
{{
|
||||
loading ? t('apiKeys.editApiKeyModal.saving') : t('apiKeys.editApiKeyModal.save')
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -655,6 +677,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { showToast } from '@/utils/toast'
|
||||
import { useClientsStore } from '@/stores/clients'
|
||||
import { useApiKeysStore } from '@/stores/apiKeys'
|
||||
@@ -674,6 +697,9 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['close', 'success'])
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// const authStore = useAuthStore()
|
||||
const clientsStore = useClientsStore()
|
||||
const apiKeysStore = useApiKeysStore()
|
||||
@@ -785,14 +811,14 @@ const updateApiKey = async () => {
|
||||
let confirmed = false
|
||||
if (window.showConfirm) {
|
||||
confirmed = await window.showConfirm(
|
||||
'费用限制提醒',
|
||||
'您设置了时间窗口但费用限制为0,这意味着不会有费用限制。\n\n是否继续?',
|
||||
'继续保存',
|
||||
'返回修改'
|
||||
t('apiKeys.editApiKeyModal.costLimitConfirmTitle'),
|
||||
t('apiKeys.editApiKeyModal.costLimitConfirmMessage'),
|
||||
t('apiKeys.editApiKeyModal.costLimitConfirmContinue'),
|
||||
t('apiKeys.editApiKeyModal.costLimitConfirmBack')
|
||||
)
|
||||
} else {
|
||||
// 降级方案
|
||||
confirmed = confirm('您设置了时间窗口但费用限制为0,这意味着不会有费用限制。\n是否继续?')
|
||||
confirmed = confirm(t('apiKeys.editApiKeyModal.costLimitConfirmMessage'))
|
||||
}
|
||||
if (!confirmed) {
|
||||
return
|
||||
@@ -898,10 +924,10 @@ const updateApiKey = async () => {
|
||||
emit('success')
|
||||
emit('close')
|
||||
} else {
|
||||
showToast(result.message || '更新失败', 'error')
|
||||
showToast(result.message || t('apiKeys.editApiKeyModal.updateFailed'), 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('更新失败', 'error')
|
||||
showToast(t('apiKeys.editApiKeyModal.updateFailed'), 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -911,23 +937,15 @@ const updateApiKey = async () => {
|
||||
const refreshAccounts = async () => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
const [
|
||||
claudeData,
|
||||
claudeConsoleData,
|
||||
geminiData,
|
||||
openaiData,
|
||||
openaiResponsesData,
|
||||
bedrockData,
|
||||
groupsData
|
||||
] = await Promise.all([
|
||||
apiClient.get('/admin/claude-accounts'),
|
||||
apiClient.get('/admin/claude-console-accounts'),
|
||||
apiClient.get('/admin/gemini-accounts'),
|
||||
apiClient.get('/admin/openai-accounts'),
|
||||
apiClient.get('/admin/openai-responses-accounts'), // 获取 OpenAI-Responses 账号
|
||||
apiClient.get('/admin/bedrock-accounts'), // 添加 Bedrock 账号获取
|
||||
apiClient.get('/admin/account-groups')
|
||||
])
|
||||
const [claudeData, claudeConsoleData, geminiData, openaiData, bedrockData, groupsData] =
|
||||
await Promise.all([
|
||||
apiClient.get('/admin/claude-accounts'),
|
||||
apiClient.get('/admin/claude-console-accounts'),
|
||||
apiClient.get('/admin/gemini-accounts'),
|
||||
apiClient.get('/admin/openai-accounts'),
|
||||
apiClient.get('/admin/bedrock-accounts'), // 添加 Bedrock 账号获取
|
||||
apiClient.get('/admin/account-groups')
|
||||
])
|
||||
|
||||
// 合并Claude OAuth账户和Claude Console账户
|
||||
const claudeAccounts = []
|
||||
@@ -961,31 +979,13 @@ const refreshAccounts = async () => {
|
||||
}))
|
||||
}
|
||||
|
||||
// 合并 OpenAI 和 OpenAI-Responses 账号
|
||||
const openaiAccounts = []
|
||||
|
||||
if (openaiData.success) {
|
||||
;(openaiData.data || []).forEach((account) => {
|
||||
openaiAccounts.push({
|
||||
...account,
|
||||
platform: 'openai',
|
||||
isDedicated: account.accountType === 'dedicated'
|
||||
})
|
||||
})
|
||||
localAccounts.value.openai = (openaiData.data || []).map((account) => ({
|
||||
...account,
|
||||
isDedicated: account.accountType === 'dedicated'
|
||||
}))
|
||||
}
|
||||
|
||||
if (openaiResponsesData.success) {
|
||||
;(openaiResponsesData.data || []).forEach((account) => {
|
||||
openaiAccounts.push({
|
||||
...account,
|
||||
platform: 'openai-responses',
|
||||
isDedicated: account.accountType === 'dedicated'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
localAccounts.value.openai = openaiAccounts
|
||||
|
||||
if (bedrockData.success) {
|
||||
localAccounts.value.bedrock = (bedrockData.data || []).map((account) => ({
|
||||
...account,
|
||||
@@ -1001,9 +1001,9 @@ const refreshAccounts = async () => {
|
||||
localAccounts.value.openaiGroups = allGroups.filter((g) => g.platform === 'openai')
|
||||
}
|
||||
|
||||
showToast('账号列表已刷新', 'success')
|
||||
showToast(t('apiKeys.editApiKeyModal.refreshAccountsSuccess'), 'success')
|
||||
} catch (error) {
|
||||
showToast('刷新账号列表失败', 'error')
|
||||
showToast(t('apiKeys.editApiKeyModal.refreshAccountsFailed'), 'error')
|
||||
} finally {
|
||||
accountsLoading.value = false
|
||||
}
|
||||
@@ -1017,7 +1017,7 @@ const loadUsers = async () => {
|
||||
availableUsers.value = response.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('Failed to load users:', error)
|
||||
console.error('Failed to load users:', error)
|
||||
availableUsers.value = [
|
||||
{
|
||||
id: 'admin',
|
||||
@@ -1043,7 +1043,7 @@ onMounted(async () => {
|
||||
supportedClients.value = clients || []
|
||||
availableTags.value = tags || []
|
||||
} catch (error) {
|
||||
// console.error('Error loading initial data:', error)
|
||||
console.error('Error loading initial data:', error)
|
||||
// Fallback to empty arrays if loading fails
|
||||
supportedClients.value = []
|
||||
availableTags.value = []
|
||||
@@ -1051,29 +1051,10 @@ onMounted(async () => {
|
||||
|
||||
// 初始化账号数据
|
||||
if (props.accounts) {
|
||||
// 合并 OpenAI 和 OpenAI-Responses 账号
|
||||
const openaiAccounts = []
|
||||
if (props.accounts.openai) {
|
||||
props.accounts.openai.forEach((account) => {
|
||||
openaiAccounts.push({
|
||||
...account,
|
||||
platform: 'openai'
|
||||
})
|
||||
})
|
||||
}
|
||||
if (props.accounts.openaiResponses) {
|
||||
props.accounts.openaiResponses.forEach((account) => {
|
||||
openaiAccounts.push({
|
||||
...account,
|
||||
platform: 'openai-responses'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
localAccounts.value = {
|
||||
claude: props.accounts.claude || [],
|
||||
gemini: props.accounts.gemini || [],
|
||||
openai: openaiAccounts,
|
||||
openai: props.accounts.openai || [],
|
||||
bedrock: props.accounts.bedrock || [], // 添加 Bedrock 账号
|
||||
claudeGroups: props.accounts.claudeGroups || [],
|
||||
geminiGroups: props.accounts.geminiGroups || [],
|
||||
@@ -1081,9 +1062,6 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 自动加载账号数据
|
||||
await refreshAccounts()
|
||||
|
||||
form.name = props.apiKey.name
|
||||
|
||||
// 处理速率限制迁移:如果有tokenLimit且没有rateLimitCost,提示用户
|
||||
@@ -1093,7 +1071,7 @@ onMounted(async () => {
|
||||
// 如果有历史tokenLimit但没有rateLimitCost,提示用户需要重新设置
|
||||
if (props.apiKey.tokenLimit > 0 && !props.apiKey.rateLimitCost) {
|
||||
// 可以根据需要添加提示,或者自动迁移(这里选择让用户手动设置)
|
||||
// console.log('检测到历史Token限制,请考虑设置费用限制')
|
||||
console.log('Token limit migration detected, consider setting cost limit')
|
||||
}
|
||||
|
||||
form.rateLimitWindow = props.apiKey.rateLimitWindow || ''
|
||||
@@ -1109,10 +1087,7 @@ onMounted(async () => {
|
||||
form.claudeAccountId = props.apiKey.claudeAccountId || ''
|
||||
}
|
||||
form.geminiAccountId = props.apiKey.geminiAccountId || ''
|
||||
|
||||
// 处理 OpenAI 账号 - 直接使用后端传来的值(已包含 responses: 前缀)
|
||||
form.openaiAccountId = props.apiKey.openaiAccountId || ''
|
||||
|
||||
form.bedrockAccountId = props.apiKey.bedrockAccountId || '' // 添加 Bedrock 账号ID初始化
|
||||
form.restrictedModels = props.apiKey.restrictedModels || []
|
||||
form.allowedClients = props.apiKey.allowedClients || []
|
||||
|
||||
Reference in New Issue
Block a user