feat: 添加两级平台筛选功能(支持平台分组)

- 添加 platformHierarchy 定义平台层级结构(Claude全部、OpenAI全部、Gemini全部、Droid)
- 添加 platformGroupMap 映射平台组到具体平台
- 添加 platformRequestHandlers 动态处理平台请求
- 将 platformOptions 从 ref 改为 computed 支持缩进显示
- 优化 loadAccounts 使用动态平台加载替代大型 switch 语句
- 新增 getPlatformsForFilter 辅助函数

功能说明:
- 支持选择"Claude(全部)"同时筛选 claude + claude-console + bedrock + ccr
- 支持选择"OpenAI(全部)"同时筛选 openai + openai-responses + azure_openai
- 支持选择"Gemini(全部)"同时筛选 gemini + gemini-api
- 保持向后兼容,仍支持单独选择具体平台
This commit is contained in:
IanShaw027
2025-12-05 03:31:13 +08:00
parent 0b3cf5112b
commit 827c0f6207

View File

@@ -2124,19 +2124,91 @@ const sortOptions = ref([
{ value: 'rateLimitTime', label: '按限流时间排序', icon: 'fa-hourglass' } { value: 'rateLimitTime', label: '按限流时间排序', icon: 'fa-hourglass' }
]) ])
const platformOptions = ref([ // 平台层级结构定义
{ value: 'all', label: '所有平台', icon: 'fa-globe' }, const platformHierarchy = [
{ value: 'claude', label: 'Claude', icon: 'fa-brain' }, {
{ value: 'claude-console', label: 'Claude Console', icon: 'fa-terminal' }, value: 'group-claude',
{ value: 'gemini', label: 'Gemini', icon: 'fab fa-google' }, label: 'Claude全部',
{ value: 'gemini-api', label: 'Gemini API', icon: 'fa-key' }, icon: 'fa-brain',
{ value: 'openai', label: 'OpenAi', icon: 'fa-openai' }, children: [
{ value: 'azure_openai', label: 'Azure OpenAI', icon: 'fab fa-microsoft' }, { value: 'claude', label: 'Claude 官方/OAuth', icon: 'fa-brain' },
{ value: 'bedrock', label: 'Bedrock', icon: 'fab fa-aws' }, { value: 'claude-console', label: 'Claude Console', icon: 'fa-terminal' },
{ value: 'openai-responses', label: 'OpenAI-Responses', icon: 'fa-server' }, { value: 'bedrock', label: 'Bedrock', icon: 'fab fa-aws' },
{ value: 'ccr', label: 'CCR', icon: 'fa-code-branch' }, { value: 'ccr', label: 'CCR Relay', icon: 'fa-code-branch' }
{ value: 'droid', label: 'Droid', icon: 'fa-robot' } ]
]) },
{
value: 'group-openai',
label: 'Codex / OpenAI全部',
icon: 'fa-openai',
children: [
{ value: 'openai', label: 'OpenAI 官方', icon: 'fa-openai' },
{ value: 'openai-responses', label: 'OpenAI-Responses (Codex)', icon: 'fa-server' },
{ value: 'azure_openai', label: 'Azure OpenAI', icon: 'fab fa-microsoft' }
]
},
{
value: 'group-gemini',
label: 'Gemini全部',
icon: 'fab fa-google',
children: [
{ value: 'gemini', label: 'Gemini OAuth', icon: 'fab fa-google' },
{ value: 'gemini-api', label: 'Gemini API', icon: 'fa-key' }
]
},
{
value: 'group-droid',
label: 'Droid全部',
icon: 'fa-robot',
children: [{ value: 'droid', label: 'Droid', icon: 'fa-robot' }]
}
]
// 平台分组映射
const platformGroupMap = {
'group-claude': ['claude', 'claude-console', 'bedrock', 'ccr'],
'group-openai': ['openai', 'openai-responses', 'azure_openai'],
'group-gemini': ['gemini', 'gemini-api'],
'group-droid': ['droid']
}
// 平台请求处理器
const platformRequestHandlers = {
claude: (params) => apiClient.get('/admin/claude-accounts', { params }),
'claude-console': (params) => apiClient.get('/admin/claude-console-accounts', { params }),
bedrock: (params) => apiClient.get('/admin/bedrock-accounts', { params }),
gemini: (params) => apiClient.get('/admin/gemini-accounts', { params }),
openai: (params) => apiClient.get('/admin/openai-accounts', { params }),
azure_openai: (params) => apiClient.get('/admin/azure-openai-accounts', { params }),
'openai-responses': (params) => apiClient.get('/admin/openai-responses-accounts', { params }),
ccr: (params) => apiClient.get('/admin/ccr-accounts', { params }),
droid: (params) => apiClient.get('/admin/droid-accounts', { params }),
'gemini-api': (params) => apiClient.get('/admin/gemini-api-accounts', { params })
}
const allPlatformKeys = Object.keys(platformRequestHandlers)
// 根据过滤器获取需要加载的平台列表
const getPlatformsForFilter = (filter) => {
if (filter === 'all') return allPlatformKeys
if (platformGroupMap[filter]) return platformGroupMap[filter]
if (allPlatformKeys.includes(filter)) return [filter]
return allPlatformKeys
}
// 平台选项(两级结构)
const platformOptions = computed(() => {
const options = [{ value: 'all', label: '所有平台', icon: 'fa-globe', indent: 0 }]
platformHierarchy.forEach((group) => {
options.push({ ...group, indent: 0, isGroup: true })
group.children?.forEach((child) => {
options.push({ ...child, indent: 1, parent: group.value })
})
})
return options
})
const statusOptions = ref([ const statusOptions = ref([
{ value: 'normal', label: '正常', icon: 'fa-check-circle' }, { value: 'normal', label: '正常', icon: 'fa-check-circle' },
@@ -2696,190 +2768,14 @@ const loadAccounts = async (forceReload = false) => {
try { try {
// 构建查询参数(用于其他筛选情况) // 构建查询参数(用于其他筛选情况)
const params = {} const params = {}
if (platformFilter.value !== 'all') { if (platformFilter.value !== 'all' && !platformGroupMap[platformFilter.value]) {
params.platform = platformFilter.value params.platform = platformFilter.value
} }
if (groupFilter.value !== 'all') { if (groupFilter.value !== 'all') {
params.groupId = groupFilter.value params.groupId = groupFilter.value
} }
// 根据平台筛选决定需要请求哪些接口 const platformsToFetch = getPlatformsForFilter(platformFilter.value)
const requests = []
if (platformFilter.value === 'all') {
// 请求所有平台
requests.push(
apiClient.get('/admin/claude-accounts', { params }),
apiClient.get('/admin/claude-console-accounts', { params }),
apiClient.get('/admin/bedrock-accounts', { params }),
apiClient.get('/admin/gemini-accounts', { params }),
apiClient.get('/admin/openai-accounts', { params }),
apiClient.get('/admin/azure-openai-accounts', { params }),
apiClient.get('/admin/openai-responses-accounts', { params }),
apiClient.get('/admin/ccr-accounts', { params }),
apiClient.get('/admin/droid-accounts', { params }),
apiClient.get('/admin/gemini-api-accounts', { params })
)
} else {
// 只请求指定平台其他平台设为null占位
switch (platformFilter.value) {
case 'claude':
requests.push(
apiClient.get('/admin/claude-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'claude-console':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
apiClient.get('/admin/claude-console-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'bedrock':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
apiClient.get('/admin/bedrock-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'gemini':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
apiClient.get('/admin/gemini-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'openai':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
apiClient.get('/admin/openai-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'azure_openai':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
apiClient.get('/admin/azure-openai-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'openai-responses':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
apiClient.get('/admin/openai-responses-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'ccr':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
apiClient.get('/admin/ccr-accounts', { params }),
Promise.resolve({ success: true, data: [] }), // droid 占位
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'droid':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
apiClient.get('/admin/droid-accounts', { params }),
Promise.resolve({ success: true, data: [] }) // gemini-api 占位
)
break
case 'gemini-api':
requests.push(
Promise.resolve({ success: true, data: [] }), // claude 占位
Promise.resolve({ success: true, data: [] }), // claude-console 占位
Promise.resolve({ success: true, data: [] }), // bedrock 占位
Promise.resolve({ success: true, data: [] }), // gemini 占位
Promise.resolve({ success: true, data: [] }), // openai 占位
Promise.resolve({ success: true, data: [] }), // azure-openai 占位
Promise.resolve({ success: true, data: [] }), // openai-responses 占位
Promise.resolve({ success: true, data: [] }), // ccr 占位
Promise.resolve({ success: true, data: [] }), // droid 占位
apiClient.get('/admin/gemini-api-accounts', { params })
)
break
default:
// 默认情况下返回空数组
requests.push(
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] }),
Promise.resolve({ success: true, data: [] })
)
break
}
}
// 使用缓存机制加载绑定计数和分组数据(不再加载完整的 API Keys 数据) // 使用缓存机制加载绑定计数和分组数据(不再加载完整的 API Keys 数据)
await Promise.all([loadBindingCounts(forceReload), loadAccountGroups(forceReload)]) await Promise.all([loadBindingCounts(forceReload), loadAccountGroups(forceReload)])
@@ -2887,125 +2783,137 @@ const loadAccounts = async (forceReload = false) => {
// 后端账户API已经包含分组信息不需要单独加载分组成员关系 // 后端账户API已经包含分组信息不需要单独加载分组成员关系
// await loadGroupMembers(forceReload) // await loadGroupMembers(forceReload)
const [ const platformResults = await Promise.all(
claudeData, platformsToFetch.map(async (platform) => {
claudeConsoleData, const handler = platformRequestHandlers[platform]
bedrockData, if (!handler) {
geminiData, return { platform, success: true, data: [] }
openaiData, }
azureOpenaiData,
openaiResponsesData,
ccrData,
droidData,
geminiApiData
] = await Promise.all(requests)
const allAccounts = [] try {
const res = await handler(params)
// 获取绑定计数数据 return { platform, success: res?.success, data: res?.data }
const counts = bindingCounts.value } catch (error) {
console.debug(`Failed to load ${platform} accounts:`, error)
if (claudeData.success) { return { platform, success: false, data: [] }
const claudeAccounts = (claudeData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
const boundApiKeysCount = counts.claudeAccountId?.[acc.id] || 0
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'claude', boundApiKeysCount }
})
allAccounts.push(...claudeAccounts)
}
if (claudeConsoleData.success) {
const claudeConsoleAccounts = (claudeConsoleData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
const boundApiKeysCount = counts.claudeConsoleAccountId?.[acc.id] || 0
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'claude-console', boundApiKeysCount }
})
allAccounts.push(...claudeConsoleAccounts)
}
if (bedrockData.success) {
const bedrockAccounts = (bedrockData.data || []).map((acc) => {
// Bedrock账户暂时不支持直接绑定
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'bedrock', boundApiKeysCount: 0 }
})
allAccounts.push(...bedrockAccounts)
}
if (geminiData.success) {
const geminiAccounts = (geminiData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
const boundApiKeysCount = counts.geminiAccountId?.[acc.id] || 0
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'gemini', boundApiKeysCount }
})
allAccounts.push(...geminiAccounts)
}
if (openaiData.success) {
const openaiAccounts = (openaiData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
const boundApiKeysCount = counts.openaiAccountId?.[acc.id] || 0
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'openai', boundApiKeysCount }
})
allAccounts.push(...openaiAccounts)
}
if (azureOpenaiData && azureOpenaiData.success) {
const azureOpenaiAccounts = (azureOpenaiData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
const boundApiKeysCount = counts.azureOpenaiAccountId?.[acc.id] || 0
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'azure_openai', boundApiKeysCount }
})
allAccounts.push(...azureOpenaiAccounts)
}
if (openaiResponsesData && openaiResponsesData.success) {
const openaiResponsesAccounts = (openaiResponsesData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
// OpenAI-Responses账户使用 responses: 前缀
const boundApiKeysCount = counts.openaiAccountId?.[`responses:${acc.id}`] || 0
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'openai-responses', boundApiKeysCount }
})
allAccounts.push(...openaiResponsesAccounts)
}
// CCR 账户
if (ccrData && ccrData.success) {
const ccrAccounts = (ccrData.data || []).map((acc) => {
// CCR 不支持 API Key 绑定,固定为 0
return { ...acc, platform: 'ccr', boundApiKeysCount: 0 }
})
allAccounts.push(...ccrAccounts)
}
// Droid 账户
if (droidData && droidData.success) {
const droidAccounts = (droidData.data || []).map((acc) => {
// 从绑定计数缓存获取数量
const boundApiKeysCount = counts.droidAccountId?.[acc.id] || acc.boundApiKeysCount || 0
return {
...acc,
platform: 'droid',
boundApiKeysCount
} }
}) })
allAccounts.push(...droidAccounts) )
const allAccounts = []
const counts = bindingCounts.value || {}
let openaiResponsesRaw = []
const appendAccounts = (platform, data) => {
if (!data || data.length === 0) return
switch (platform) {
case 'claude': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.claudeAccountId?.[acc.id] || 0
return { ...acc, platform: 'claude', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
case 'claude-console': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.claudeConsoleAccountId?.[acc.id] || 0
return { ...acc, platform: 'claude-console', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
case 'bedrock': {
const items = data.map((acc) => ({ ...acc, platform: 'bedrock', boundApiKeysCount: 0 }))
allAccounts.push(...items)
break
}
case 'gemini': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.geminiAccountId?.[acc.id] || 0
return { ...acc, platform: 'gemini', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
case 'openai': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.openaiAccountId?.[acc.id] || 0
return { ...acc, platform: 'openai', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
case 'azure_openai': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.azureOpenaiAccountId?.[acc.id] || 0
return { ...acc, platform: 'azure_openai', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
case 'openai-responses': {
openaiResponsesRaw = data
break
}
case 'ccr': {
const items = data.map((acc) => ({ ...acc, platform: 'ccr', boundApiKeysCount: 0 }))
allAccounts.push(...items)
break
}
case 'droid': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.droidAccountId?.[acc.id] || acc.boundApiKeysCount || 0
return { ...acc, platform: 'droid', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
case 'gemini-api': {
const items = data.map((acc) => {
const boundApiKeysCount = counts.geminiAccountId?.[`api:${acc.id}`] || 0
return { ...acc, platform: 'gemini-api', boundApiKeysCount }
})
allAccounts.push(...items)
break
}
default:
break
}
} }
// Gemini API 账户 platformResults.forEach(({ platform, success, data }) => {
if (geminiApiData && geminiApiData.success) { if (success) {
const geminiApiAccounts = (geminiApiData.data || []).map((acc) => { appendAccounts(platform, data || [])
// 从绑定计数缓存获取数量 }
// Gemini-API账户使用 api: 前缀 })
const boundApiKeysCount = counts.geminiAccountId?.[`api:${acc.id}`] || 0
// 后端已经包含了groupInfos直接使用 if (openaiResponsesRaw.length > 0) {
return { ...acc, platform: 'gemini-api', boundApiKeysCount } let autoRecoveryConfigMap = {}
try {
const configsRes = await apiClient.get(
'/admin/openai-responses-accounts/auto-recovery-configs'
)
if (configsRes.success && Array.isArray(configsRes.data)) {
autoRecoveryConfigMap = configsRes.data.reduce((map, config) => {
if (config?.accountId) {
map[config.accountId] = config
}
return map
}, {})
}
} catch (error) {
console.debug('Failed to load auto-recovery configs:', error)
}
const responsesAccounts = openaiResponsesRaw.map((acc) => {
const boundApiKeysCount = counts.openaiAccountId?.[`responses:${acc.id}`] || 0
const autoRecoveryConfig = autoRecoveryConfigMap[acc.id] || acc.autoRecoveryConfig || null
return { ...acc, platform: 'openai-responses', boundApiKeysCount, autoRecoveryConfig }
}) })
allAccounts.push(...geminiApiAccounts)
allAccounts.push(...responsesAccounts)
} }
// 根据分组筛选器过滤账户 // 根据分组筛选器过滤账户