feat: 完善API Keys页面多平台账户绑定信息展示

- 支持显示Claude、Gemini和OpenAI三个平台的账户绑定信息
- 添加账户状态提醒(不存在、专属、分组、共享池)
- 优化UI设计,使用彩色标签区分不同平台
- 改进响应式布局,适配移动端和平板设备
- 修复OpenAI账户绑定数量统计问题
- 修复删除账户时OpenAI绑定检查逻辑

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-08-12 15:01:56 +08:00
parent b06fa5efe8
commit b3cba9e091
2 changed files with 195 additions and 23 deletions

View File

@@ -1010,8 +1010,12 @@ const loadAccounts = async (forceReload = false) => {
} }
if (openaiData.success) { if (openaiData.success) {
const openaiAccounts = (openaiData.data || []).map((acc) => { const openaiAccounts = (openaiData.data || []).map((acc) => {
// 计算每个OpenAI账户绑定的API Key数量
const boundApiKeysCount = apiKeys.value.filter(
(key) => key.openaiAccountId === acc.id
).length
const groupInfo = accountGroupMap.value.get(acc.id) || null const groupInfo = accountGroupMap.value.get(acc.id) || null
return { ...acc, platform: 'openai', boundApiKeysCount: 0, groupInfo } return { ...acc, platform: 'openai', boundApiKeysCount, groupInfo }
}) })
allAccounts.push(...openaiAccounts) allAccounts.push(...openaiAccounts)
} }
@@ -1210,7 +1214,10 @@ const editAccount = (account) => {
const deleteAccount = async (account) => { const deleteAccount = async (account) => {
// 检查是否有API Key绑定到此账号 // 检查是否有API Key绑定到此账号
const boundKeysCount = apiKeys.value.filter( const boundKeysCount = apiKeys.value.filter(
(key) => key.claudeAccountId === account.id || key.geminiAccountId === account.id (key) =>
key.claudeAccountId === account.id ||
key.geminiAccountId === account.id ||
key.openaiAccountId === account.id
).length ).length
if (boundKeysCount > 0) { if (boundKeysCount > 0) {

View File

@@ -206,18 +206,60 @@
<div class="truncate text-xs text-gray-500" :title="key.id"> <div class="truncate text-xs text-gray-500" :title="key.id">
{{ key.id }} {{ key.id }}
</div> </div>
<div class="mt-1 truncate text-xs text-gray-500"> <!-- 账户绑定信息 -->
<span <div class="mt-1.5 space-y-1">
v-if="key.claudeAccountId" <!-- Claude 绑定 -->
:title="`绑定: ${getBoundAccountName(key.claudeAccountId)}`" <div
v-if="key.claudeAccountId || key.claudeConsoleAccountId"
class="flex items-center gap-1 text-xs"
> >
<i class="fas fa-link mr-1" /> <span
{{ getBoundAccountName(key.claudeAccountId) }} class="inline-flex items-center rounded bg-indigo-100 px-1.5 py-0.5 text-indigo-700"
>
<i class="fas fa-brain mr-1 text-[10px]" />
Claude
</span> </span>
<span v-else> <span class="truncate text-gray-600">
{{ getClaudeBindingInfo(key) }}
</span>
</div>
<!-- Gemini 绑定 -->
<div v-if="key.geminiAccountId" class="flex items-center gap-1 text-xs">
<span
class="inline-flex items-center rounded bg-yellow-100 px-1.5 py-0.5 text-yellow-700"
>
<i class="fas fa-robot mr-1 text-[10px]" />
Gemini
</span>
<span class="truncate text-gray-600">
{{ getGeminiBindingInfo(key) }}
</span>
</div>
<!-- OpenAI 绑定 -->
<div v-if="key.openaiAccountId" class="flex items-center gap-1 text-xs">
<span
class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-gray-700"
>
<i class="fa-openai mr-1 text-[10px]" />
OpenAI
</span>
<span class="truncate text-gray-600">
{{ getOpenAIBindingInfo(key) }}
</span>
</div>
<!-- 无绑定时显示共享池 -->
<div
v-if="
!key.claudeAccountId &&
!key.claudeConsoleAccountId &&
!key.geminiAccountId &&
!key.openaiAccountId
"
class="text-xs text-gray-500"
>
<i class="fas fa-share-alt mr-1" /> <i class="fas fa-share-alt mr-1" />
共享池 使用共享池
</span> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -712,16 +754,58 @@
</span> </span>
</div> </div>
<!-- 绑定信息 --> <!-- 账户绑定信息 -->
<div class="mb-3 text-xs text-gray-600"> <div class="mb-3 space-y-1.5">
<span v-if="key.claudeAccountId || key.claudeConsoleAccountId"> <!-- Claude 绑定 -->
<i class="fas fa-link mr-1" /> <div
绑定: {{ getBoundAccountName(key.claudeAccountId, key.claudeConsoleAccountId) }} v-if="key.claudeAccountId || key.claudeConsoleAccountId"
class="flex flex-wrap items-center gap-1 text-xs"
>
<span
class="inline-flex items-center rounded bg-indigo-100 px-2 py-0.5 text-indigo-700"
>
<i class="fas fa-brain mr-1" />
Claude
</span> </span>
<span v-else> <span class="text-gray-600">
{{ getClaudeBindingInfo(key) }}
</span>
</div>
<!-- Gemini 绑定 -->
<div v-if="key.geminiAccountId" class="flex flex-wrap items-center gap-1 text-xs">
<span
class="inline-flex items-center rounded bg-yellow-100 px-2 py-0.5 text-yellow-700"
>
<i class="fas fa-robot mr-1" />
Gemini
</span>
<span class="text-gray-600">
{{ getGeminiBindingInfo(key) }}
</span>
</div>
<!-- OpenAI 绑定 -->
<div v-if="key.openaiAccountId" class="flex flex-wrap items-center gap-1 text-xs">
<span class="inline-flex items-center rounded bg-gray-100 px-2 py-0.5 text-gray-700">
<i class="fa-openai mr-1" />
OpenAI
</span>
<span class="text-gray-600">
{{ getOpenAIBindingInfo(key) }}
</span>
</div>
<!-- 无绑定时显示共享池 -->
<div
v-if="
!key.claudeAccountId &&
!key.claudeConsoleAccountId &&
!key.geminiAccountId &&
!key.openaiAccountId
"
class="text-xs text-gray-500"
>
<i class="fas fa-share-alt mr-1" /> <i class="fas fa-share-alt mr-1" />
使用共享池 使用共享池
</span> </div>
</div> </div>
<!-- 统计信息 --> <!-- 统计信息 -->
@@ -1300,6 +1384,12 @@ const getBoundAccountName = (accountId) => {
return `分组-${geminiGroup.name}` return `分组-${geminiGroup.name}`
} }
// 从OpenAI分组中查找
const openaiGroup = accounts.value.openaiGroups.find((g) => g.id === groupId)
if (openaiGroup) {
return `分组-${openaiGroup.name}`
}
// 如果找不到分组返回分组ID的前8位 // 如果找不到分组返回分组ID的前8位
return `分组-${groupId.substring(0, 8)}` return `分组-${groupId.substring(0, 8)}`
} }
@@ -1307,17 +1397,92 @@ const getBoundAccountName = (accountId) => {
// 从Claude账户列表中查找 // 从Claude账户列表中查找
const claudeAccount = accounts.value.claude.find((acc) => acc.id === accountId) const claudeAccount = accounts.value.claude.find((acc) => acc.id === accountId)
if (claudeAccount) { if (claudeAccount) {
return `账户-${claudeAccount.name}` return `${claudeAccount.name}`
} }
// 从Gemini账户列表中查找 // 从Gemini账户列表中查找
const geminiAccount = accounts.value.gemini.find((acc) => acc.id === accountId) const geminiAccount = accounts.value.gemini.find((acc) => acc.id === accountId)
if (geminiAccount) { if (geminiAccount) {
return `账户-${geminiAccount.name}` return `${geminiAccount.name}`
}
// 从OpenAI账户列表中查找
const openaiAccount = accounts.value.openai.find((acc) => acc.id === accountId)
if (openaiAccount) {
return `${openaiAccount.name}`
} }
// 如果找不到返回账户ID的前8位 // 如果找不到返回账户ID的前8位
return `账户-${accountId.substring(0, 8)}` return `${accountId.substring(0, 8)}`
}
// 获取Claude绑定信息
const getClaudeBindingInfo = (key) => {
if (key.claudeAccountId) {
const info = getBoundAccountName(key.claudeAccountId)
if (key.claudeAccountId.startsWith('group:')) {
return info
}
// 检查账户是否存在
const account = accounts.value.claude.find((acc) => acc.id === key.claudeAccountId)
if (!account) {
return `⚠️ ${info} (账户不存在)`
}
if (account.accountType === 'dedicated') {
return `🔒 专属-${info}`
}
return info
}
if (key.claudeConsoleAccountId) {
const account = accounts.value.claude.find(
(acc) => acc.id === key.claudeConsoleAccountId && acc.platform === 'claude-console'
)
if (!account) {
return `⚠️ Console账户不存在`
}
return `Console-${account.name}`
}
return ''
}
// 获取Gemini绑定信息
const getGeminiBindingInfo = (key) => {
if (key.geminiAccountId) {
const info = getBoundAccountName(key.geminiAccountId)
if (key.geminiAccountId.startsWith('group:')) {
return info
}
// 检查账户是否存在
const account = accounts.value.gemini.find((acc) => acc.id === key.geminiAccountId)
if (!account) {
return `⚠️ ${info} (账户不存在)`
}
if (account.accountType === 'dedicated') {
return `🔒 专属-${info}`
}
return info
}
return ''
}
// 获取OpenAI绑定信息
const getOpenAIBindingInfo = (key) => {
if (key.openaiAccountId) {
const info = getBoundAccountName(key.openaiAccountId)
if (key.openaiAccountId.startsWith('group:')) {
return info
}
// 检查账户是否存在
const account = accounts.value.openai.find((acc) => acc.id === key.openaiAccountId)
if (!account) {
return `⚠️ ${info} (账户不存在)`
}
if (account.accountType === 'dedicated') {
return `🔒 专属-${info}`
}
return info
}
return ''
} }
// 检查API Key是否过期 // 检查API Key是否过期