fix: 修复CCR账户表单验证和平台分组逻辑

- 修复CCR账户创建时的表单字段验证
- 统一CCR与Claude Console的处理逻辑
- 修复账户删除前的API Key绑定检查
- 修复Claude Console账户绑定的API Key计数
- 优化平台分组判断逻辑

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-09-11 11:54:27 +08:00
parent fef2c8c3c2
commit 02989a7588
3 changed files with 58 additions and 21 deletions

View File

@@ -456,10 +456,10 @@
v-if=" v-if="
!isEdit && !isEdit &&
form.platform !== 'claude-console' && form.platform !== 'claude-console' &&
form.platform !== 'ccr' &&
form.platform !== 'bedrock' && form.platform !== 'bedrock' &&
form.platform !== 'azure_openai' && form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses' && form.platform !== 'openai-responses'
form.platform !== 'ccr'
" "
> >
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300" <label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
@@ -950,8 +950,11 @@
</div> </div>
</div> </div>
<!-- Claude Console 特定字段 --> <!-- Claude Console 和 CCR 特定字段 -->
<div v-if="form.platform === 'claude-console' && !isEdit" class="space-y-4"> <div
v-if="(form.platform === 'claude-console' || form.platform === 'ccr') && !isEdit"
class="space-y-4"
>
<div> <div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300" <label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>API URL *</label >API URL *</label
@@ -1405,6 +1408,7 @@
v-if=" v-if="
form.addType === 'manual' && form.addType === 'manual' &&
form.platform !== 'claude-console' && form.platform !== 'claude-console' &&
form.platform !== 'ccr' &&
form.platform !== 'bedrock' && form.platform !== 'bedrock' &&
form.platform !== 'azure_openai' && form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses' form.platform !== 'openai-responses'
@@ -1565,6 +1569,7 @@
v-if=" v-if="
(form.addType === 'oauth' || form.addType === 'setup-token') && (form.addType === 'oauth' || form.addType === 'setup-token') &&
form.platform !== 'claude-console' && form.platform !== 'claude-console' &&
form.platform !== 'ccr' &&
form.platform !== 'bedrock' && form.platform !== 'bedrock' &&
form.platform !== 'azure_openai' && form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses' form.platform !== 'openai-responses'
@@ -2080,8 +2085,11 @@
</p> </p>
</div> </div>
<!-- Claude Console 特定字段(编辑模式)--> <!-- Claude Console 和 CCR 特定字段(编辑模式)-->
<div v-if="form.platform === 'claude-console'" class="space-y-4"> <div
v-if="form.platform === 'claude-console' || form.platform === 'ccr'"
class="space-y-4"
>
<div> <div>
<label class="mb-3 block text-sm font-semibold text-gray-700">API URL</label> <label class="mb-3 block text-sm font-semibold text-gray-700">API URL</label>
<input <input
@@ -2608,6 +2616,7 @@
<div <div
v-if=" v-if="
form.platform !== 'claude-console' && form.platform !== 'claude-console' &&
form.platform !== 'ccr' &&
form.platform !== 'bedrock' && form.platform !== 'bedrock' &&
form.platform !== 'azure_openai' && form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses' form.platform !== 'openai-responses'
@@ -2752,7 +2761,7 @@ const platformGroup = ref('')
// 根据现有平台确定分组 // 根据现有平台确定分组
const determinePlatformGroup = (platform) => { const determinePlatformGroup = (platform) => {
if (['claude', 'claude-console', 'bedrock'].includes(platform)) { if (['claude', 'claude-console', 'ccr', 'bedrock'].includes(platform)) {
return 'claude' return 'claude'
} else if (['openai', 'openai-responses', 'azure_openai'].includes(platform)) { } else if (['openai', 'openai-responses', 'azure_openai'].includes(platform)) {
return 'openai' return 'openai'
@@ -3238,6 +3247,18 @@ const createAccount = async () => {
} }
} }
// CCR (Claude Code Router) 验证 - 使用与 Claude Console 相同的字段
if (form.value.platform === 'ccr') {
if (!form.value.apiUrl || form.value.apiUrl.trim() === '') {
errors.value.apiUrl = '请填写 API URL'
hasError = true
}
if (!form.value.apiKey || form.value.apiKey.trim() === '') {
errors.value.apiKey = '请填写 API Key'
hasError = true
}
}
// OpenAI-Responses 验证 // OpenAI-Responses 验证
if (form.value.platform === 'openai-responses') { if (form.value.platform === 'openai-responses') {
if (!form.value.baseApi || form.value.baseApi.trim() === '') { if (!form.value.baseApi || form.value.baseApi.trim() === '') {
@@ -3277,7 +3298,7 @@ const createAccount = async () => {
hasError = true hasError = true
} }
} else if (form.value.addType === 'manual') { } else if (form.value.addType === 'manual') {
// 手动模式验证 // 手动模式验证 - 只有部分平台需要验证 Token
if (form.value.platform === 'openai') { if (form.value.platform === 'openai') {
// OpenAI 平台必须有 Refresh Token // OpenAI 平台必须有 Refresh Token
if (!form.value.refreshToken || form.value.refreshToken.trim() === '') { if (!form.value.refreshToken || form.value.refreshToken.trim() === '') {
@@ -3285,13 +3306,20 @@ const createAccount = async () => {
hasError = true hasError = true
} }
// Access Token 可选,如果没有会通过 Refresh Token 获取 // Access Token 可选,如果没有会通过 Refresh Token 获取
} else { } else if (form.value.platform === 'gemini') {
// 其他平台(Gemini需要 Access Token // Gemini 平台需要 Access Token
if (!form.value.accessToken || form.value.accessToken.trim() === '') {
errors.value.accessToken = '请填写 Access Token'
hasError = true
}
} else if (form.value.platform === 'claude') {
// Claude 平台需要 Access Token
if (!form.value.accessToken || form.value.accessToken.trim() === '') { if (!form.value.accessToken || form.value.accessToken.trim() === '') {
errors.value.accessToken = '请填写 Access Token' errors.value.accessToken = '请填写 Access Token'
hasError = true hasError = true
} }
} }
// Claude Console、CCR、OpenAI-Responses 等其他平台不需要 Token 验证
} }
// 分组类型验证 - 创建账户流程修复 // 分组类型验证 - 创建账户流程修复
@@ -3413,8 +3441,8 @@ const createAccount = async () => {
data.needsImmediateRefresh = true data.needsImmediateRefresh = true
data.requireRefreshSuccess = true // 必须刷新成功才能创建账户 data.requireRefreshSuccess = true // 必须刷新成功才能创建账户
data.priority = form.value.priority || 50 data.priority = form.value.priority || 50
} else if (form.value.platform === 'claude-console') { } else if (form.value.platform === 'claude-console' || form.value.platform === 'ccr') {
// Claude Console 账户特定数据 // Claude Console 和 CCR 账户特定数据CCR 使用 Claude Console 的后端逻辑)
data.apiUrl = form.value.apiUrl data.apiUrl = form.value.apiUrl
data.apiKey = form.value.apiKey data.apiKey = form.value.apiKey
data.priority = form.value.priority || 50 data.priority = form.value.priority || 50
@@ -3464,7 +3492,8 @@ const createAccount = async () => {
let result let result
if (form.value.platform === 'claude') { if (form.value.platform === 'claude') {
result = await accountsStore.createClaudeAccount(data) result = await accountsStore.createClaudeAccount(data)
} else if (form.value.platform === 'claude-console') { } else if (form.value.platform === 'claude-console' || form.value.platform === 'ccr') {
// CCR 使用 Claude Console 的后端 API
result = await accountsStore.createClaudeConsoleAccount(data) result = await accountsStore.createClaudeConsoleAccount(data)
} else if (form.value.platform === 'openai-responses') { } else if (form.value.platform === 'openai-responses') {
result = await accountsStore.createOpenAIResponsesAccount(data) result = await accountsStore.createOpenAIResponsesAccount(data)
@@ -3841,8 +3870,8 @@ const showGroupManagement = ref(false)
// 根据平台筛选分组 // 根据平台筛选分组
const filteredGroups = computed(() => { const filteredGroups = computed(() => {
let platformFilter = form.value.platform let platformFilter = form.value.platform
// Claude Console 使用 Claude 分组 // Claude Console 和 CCR 使用 Claude 分组
if (form.value.platform === 'claude-console') { if (form.value.platform === 'claude-console' || form.value.platform === 'ccr') {
platformFilter = 'claude' platformFilter = 'claude'
} }
// OpenAI-Responses 使用 OpenAI 分组 // OpenAI-Responses 使用 OpenAI 分组
@@ -3889,10 +3918,11 @@ watch(
// 处理添加方式的自动切换 // 处理添加方式的自动切换
if ( if (
newPlatform === 'claude-console' || newPlatform === 'claude-console' ||
newPlatform === 'ccr' ||
newPlatform === 'bedrock' || newPlatform === 'bedrock' ||
newPlatform === 'openai-responses' newPlatform === 'openai-responses'
) { ) {
form.value.addType = 'manual' // Claude Console、Bedrock 和 OpenAI-Responses 只支持手动模式 form.value.addType = 'manual' // Claude Console、CCR、Bedrock 和 OpenAI-Responses 只支持手动模式
} else if (newPlatform === 'claude') { } else if (newPlatform === 'claude') {
// 切换到 Claude 时,使用 Setup Token 作为默认方式 // 切换到 Claude 时,使用 Setup Token 作为默认方式
form.value.addType = 'setup-token' form.value.addType = 'setup-token'

View File

@@ -346,7 +346,7 @@ const submit = async () => {
} }
const res = await apiClient.put(`/admin/ccr-accounts/${props.account.id}`, updates) const res = await apiClient.put(`/admin/ccr-accounts/${props.account.id}`, updates)
if (res.success) { if (res.success) {
showToast('保存成功', 'success') // 不在这里显示 toast由父组件统一处理
emit('success') emit('success')
} else { } else {
showToast(res.message || '保存失败', 'error') showToast(res.message || '保存失败', 'error')
@@ -369,7 +369,7 @@ const submit = async () => {
} }
const res = await apiClient.post('/admin/ccr-accounts', payload) const res = await apiClient.post('/admin/ccr-accounts', payload)
if (res.success) { if (res.success) {
showToast('创建成功', 'success') // 不在这里显示 toast由父组件统一处理
emit('success') emit('success')
} else { } else {
showToast(res.message || '创建失败', 'error') showToast(res.message || '创建失败', 'error')

View File

@@ -492,6 +492,7 @@
account.platform === 'bedrock' || account.platform === 'bedrock' ||
account.platform === 'gemini' || account.platform === 'gemini' ||
account.platform === 'openai' || account.platform === 'openai' ||
account.platform === 'openai-responses' ||
account.platform === 'azure_openai' || account.platform === 'azure_openai' ||
account.platform === 'ccr' account.platform === 'ccr'
" "
@@ -1295,9 +1296,12 @@ const loadAccounts = async (forceReload = false) => {
if (claudeConsoleData.success) { if (claudeConsoleData.success) {
const claudeConsoleAccounts = (claudeConsoleData.data || []).map((acc) => { const claudeConsoleAccounts = (claudeConsoleData.data || []).map((acc) => {
// Claude Console账户暂时不支持直接绑定 // 计算每个Claude Console账户绑定的API Key数量
const boundApiKeysCount = apiKeys.value.filter(
(key) => key.claudeConsoleAccountId === acc.id
).length
// 后端已经包含了groupInfos直接使用 // 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'claude-console', boundApiKeysCount: 0 } return { ...acc, platform: 'claude-console', boundApiKeysCount }
}) })
allAccounts.push(...claudeConsoleAccounts) allAccounts.push(...claudeConsoleAccounts)
} }
@@ -1594,8 +1598,11 @@ const deleteAccount = async (account) => {
const boundKeysCount = apiKeys.value.filter( const boundKeysCount = apiKeys.value.filter(
(key) => (key) =>
key.claudeAccountId === account.id || key.claudeAccountId === account.id ||
key.claudeConsoleAccountId === account.id ||
key.geminiAccountId === account.id || key.geminiAccountId === account.id ||
key.openaiAccountId === account.id key.openaiAccountId === account.id ||
key.azureOpenaiAccountId === account.id ||
key.openaiAccountId === `responses:${account.id}`
).length ).length
if (boundKeysCount > 0) { if (boundKeysCount > 0) {