feat: 实现账户多分组调度功能

- 添加账户分组管理功能,支持创建、编辑、删除分组
- 实现基于分组的账户调度逻辑
- 添加分组权重和优先级支持
- 提供测试脚本验证多分组调度功能
- 修复代码格式化问题(统一使用LF换行符)

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sczheng189
2025-08-25 18:58:08 +08:00
parent 81ad8a787f
commit e69ab2161d
7 changed files with 567 additions and 104 deletions

View File

@@ -3723,7 +3723,6 @@
"resolved": "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
"integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.21.3"
},

View File

@@ -240,12 +240,14 @@
>
<i class="fas fa-share-alt mr-1" />共享
</span>
<!-- 显示所有分组 -->
<span
v-if="account.groupInfo"
v-for="group in account.groupInfos"
:key="group.id"
class="ml-1 inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-400"
:title="`所属分组: ${account.groupInfo.name}`"
:title="`所属分组: ${group.name}`"
>
<i class="fas fa-folder mr-1" />{{ account.groupInfo.name }}
<i class="fas fa-folder mr-1" />{{ group.name }}
</span>
</div>
<div
@@ -824,7 +826,7 @@ const platformFilter = ref('all')
const apiKeysLoaded = ref(false)
const groupsLoaded = ref(false)
const groupMembersLoaded = ref(false)
const accountGroupMap = ref(new Map())
const accountGroupMap = ref(new Map()) // Map<accountId, Array<groupInfo>>
// 下拉选项数据
const sortOptions = ref([
@@ -978,8 +980,8 @@ const loadAccounts = async (forceReload = false) => {
// 使用缓存机制加载 API Keys 和分组数据
await Promise.all([loadApiKeys(forceReload), loadAccountGroups(forceReload)])
// 加载分组成员关系(需要在分组数据加载完成后)
await loadGroupMembers(forceReload)
// 后端账户API已经包含分组信息不需要单独加载分组成员关系
// await loadGroupMembers(forceReload)
const [claudeData, claudeConsoleData, bedrockData, geminiData, openaiData, azureOpenaiData] =
await Promise.all(requests)
@@ -992,9 +994,8 @@ const loadAccounts = async (forceReload = false) => {
const boundApiKeysCount = apiKeys.value.filter(
(key) => key.claudeAccountId === acc.id
).length
// 检查是否属于某个分组
const groupInfo = accountGroupMap.value.get(acc.id) || null
return { ...acc, platform: 'claude', boundApiKeysCount, groupInfo }
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'claude', boundApiKeysCount }
})
allAccounts.push(...claudeAccounts)
}
@@ -1002,8 +1003,8 @@ const loadAccounts = async (forceReload = false) => {
if (claudeConsoleData.success) {
const claudeConsoleAccounts = (claudeConsoleData.data || []).map((acc) => {
// Claude Console账户暂时不支持直接绑定
const groupInfo = accountGroupMap.value.get(acc.id) || null
return { ...acc, platform: 'claude-console', boundApiKeysCount: 0, groupInfo }
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'claude-console', boundApiKeysCount: 0 }
})
allAccounts.push(...claudeConsoleAccounts)
}
@@ -1011,8 +1012,8 @@ const loadAccounts = async (forceReload = false) => {
if (bedrockData.success) {
const bedrockAccounts = (bedrockData.data || []).map((acc) => {
// Bedrock账户暂时不支持直接绑定
const groupInfo = accountGroupMap.value.get(acc.id) || null
return { ...acc, platform: 'bedrock', boundApiKeysCount: 0, groupInfo }
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'bedrock', boundApiKeysCount: 0 }
})
allAccounts.push(...bedrockAccounts)
}
@@ -1023,8 +1024,8 @@ const loadAccounts = async (forceReload = false) => {
const boundApiKeysCount = apiKeys.value.filter(
(key) => key.geminiAccountId === acc.id
).length
const groupInfo = accountGroupMap.value.get(acc.id) || null
return { ...acc, platform: 'gemini', boundApiKeysCount, groupInfo }
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'gemini', boundApiKeysCount }
})
allAccounts.push(...geminiAccounts)
}
@@ -1034,8 +1035,8 @@ const loadAccounts = async (forceReload = false) => {
const boundApiKeysCount = apiKeys.value.filter(
(key) => key.openaiAccountId === acc.id
).length
const groupInfo = accountGroupMap.value.get(acc.id) || null
return { ...acc, platform: 'openai', boundApiKeysCount, groupInfo }
// 后端已经包含了groupInfos直接使用
return { ...acc, platform: 'openai', boundApiKeysCount }
})
allAccounts.push(...openaiAccounts)
}
@@ -1131,36 +1132,6 @@ const loadAccountGroups = async (forceReload = false) => {
}
}
// 加载分组成员关系(缓存版本)
const loadGroupMembers = async (forceReload = false) => {
if (!forceReload && groupMembersLoaded.value) {
return // 使用缓存数据
}
try {
// 重置映射
accountGroupMap.value.clear()
// 获取所有分组的成员信息
for (const group of accountGroups.value) {
try {
const membersResponse = await apiClient.get(`/admin/account-groups/${group.id}/members`)
if (membersResponse.success) {
const members = membersResponse.data || []
members.forEach((member) => {
accountGroupMap.value.set(member.id, group)
})
}
} catch (error) {
console.error(`Failed to load members for group ${group.id}:`, error)
}
}
groupMembersLoaded.value = true
} catch (error) {
console.error('Failed to load group members:', error)
}
}
// 清空缓存的函数
const clearCache = () => {
apiKeysLoaded.value = false