Add composite group routing

This commit is contained in:
Heatherm Huang
2026-07-23 09:19:24 +08:00
parent fa2da0409e
commit ebc1028771
30 changed files with 640 additions and 67 deletions
@@ -94,11 +94,11 @@ const filteredGroups = computed(() => {
// antigravity 账户启用混合调度后,可选择 anthropic/gemini 分组
if (props.platform === 'antigravity' && props.mixedScheduling) {
result = result.filter(
(g) => g.platform === 'antigravity' || g.platform === 'anthropic' || g.platform === 'gemini'
(g) => g.platform === 'antigravity' || g.platform === 'anthropic' || g.platform === 'gemini' || g.platform === 'composite'
)
} else {
// 默认:只能选择同 platform 的分组
result = result.filter((g) => g.platform === props.platform)
// 默认:只能选择同 platform 的分组;composite 分组可接收任意具体平台账号
result = result.filter((g) => g.platform === props.platform || g.platform === 'composite')
}
}
if (isSearchable.value && searchText.value) {
@@ -25,6 +25,13 @@
d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"
/>
</svg>
<!-- Composite group icon -->
<svg v-else-if="platform === 'composite'" :class="sizeClass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="6" cy="12" r="3" />
<circle cx="18" cy="6" r="3" />
<circle cx="18" cy="18" r="3" />
<path stroke-linecap="round" stroke-linejoin="round" d="M8.7 10.7 15.3 7.3M8.7 13.3l6.6 3.4" />
</svg>
<!-- Fallback: generic platform icon -->
<svg v-else :class="sizeClass" fill="currentColor" viewBox="0 0 24 24">
<path
@@ -944,6 +944,7 @@ export default {
gemini: 'Gemini',
antigravity: 'Antigravity',
grok: 'Grok',
composite: 'Composite',
},
deleteConfirm:
"Are you sure you want to delete '{name}'? All associated API keys will no longer belong to any group.",
@@ -877,6 +877,7 @@ export default {
gemini: 'Gemini',
antigravity: 'Antigravity',
grok: 'Grok',
composite: 'Composite',
},
saving: '保存中...',
noGroups: '暂无分组',
+1 -1
View File
@@ -492,7 +492,7 @@ export interface PaginationConfig {
// ==================== API Key & Group Types ====================
export type GroupPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'grok'
export type GroupPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'grok' | 'composite'
export type SubscriptionType = 'standard' | 'subscription'
+14 -2
View File
@@ -5,7 +5,7 @@
* instead of defining their own color mappings.
*/
export type Platform = 'anthropic' | 'openai' | 'antigravity' | 'gemini' | 'grok'
export type Platform = 'anthropic' | 'openai' | 'antigravity' | 'gemini' | 'grok' | 'composite'
// ── Badge (bg + text + border, for inline badges with border) ───────
const BADGE: Record<Platform, string> = {
@@ -14,6 +14,7 @@ const BADGE: Record<Platform, string> = {
antigravity: 'bg-purple-500/10 text-purple-600 border-purple-500/30 dark:text-purple-400',
gemini: 'bg-blue-500/10 text-blue-600 border-blue-500/30 dark:text-blue-400',
grok: 'bg-zinc-800/10 text-zinc-800 border-zinc-800/30 dark:bg-zinc-500/10 dark:text-zinc-200 dark:border-zinc-500/30',
composite: 'bg-cyan-500/10 text-cyan-700 border-cyan-500/30 dark:text-cyan-300',
}
const BADGE_DEFAULT = 'bg-slate-500/10 text-slate-600 border-slate-500/30 dark:text-slate-400'
@@ -24,6 +25,7 @@ const BADGE_LIGHT: Record<Platform, string> = {
antigravity: 'bg-purple-500/10 text-purple-600 dark:bg-purple-500/10 dark:text-purple-300',
gemini: 'bg-blue-500/10 text-blue-600 dark:bg-blue-500/10 dark:text-blue-300',
grok: 'bg-zinc-800/10 text-zinc-800 dark:bg-zinc-500/10 dark:text-zinc-200',
composite: 'bg-cyan-500/10 text-cyan-700 dark:bg-cyan-500/10 dark:text-cyan-300',
}
// ── Border ──────────────────────────────────────────────────────────
@@ -33,6 +35,7 @@ const BORDER: Record<Platform, string> = {
antigravity: 'border-purple-500/20 dark:border-purple-500/20',
gemini: 'border-blue-500/20 dark:border-blue-500/20',
grok: 'border-zinc-800/20 dark:border-zinc-500/20',
composite: 'border-cyan-500/20 dark:border-cyan-500/20',
}
const BORDER_DEFAULT = 'border-gray-200 dark:border-dark-700'
@@ -43,6 +46,7 @@ const ACCENT_BAR: Record<Platform, string> = {
antigravity: 'bg-gradient-to-r from-purple-400 to-purple-500',
gemini: 'bg-gradient-to-r from-blue-400 to-blue-500',
grok: 'bg-gradient-to-r from-zinc-700 to-zinc-900',
composite: 'bg-gradient-to-r from-slate-500 to-cyan-500',
}
const ACCENT_BAR_DEFAULT = 'bg-gradient-to-r from-primary-400 to-primary-500'
@@ -53,6 +57,7 @@ const TEXT: Record<Platform, string> = {
antigravity: 'text-purple-600 dark:text-purple-400',
gemini: 'text-blue-600 dark:text-blue-400',
grok: 'text-zinc-800 dark:text-zinc-200',
composite: 'text-cyan-700 dark:text-cyan-300',
}
const TEXT_DEFAULT = 'text-primary-600 dark:text-primary-400'
@@ -63,6 +68,7 @@ const ICON: Record<Platform, string> = {
antigravity: 'text-purple-500 dark:text-purple-400',
gemini: 'text-blue-500 dark:text-blue-400',
grok: 'text-zinc-800 dark:text-zinc-200',
composite: 'text-cyan-600 dark:text-cyan-300',
}
const ICON_DEFAULT = 'text-primary-500 dark:text-primary-400'
@@ -73,6 +79,7 @@ const BUTTON: Record<Platform, string> = {
antigravity: 'bg-purple-500 text-white hover:bg-purple-600 active:bg-purple-700 dark:bg-purple-500/80 dark:hover:bg-purple-500',
gemini: 'bg-blue-500 text-white hover:bg-blue-600 active:bg-blue-700 dark:bg-blue-500/80 dark:hover:bg-blue-500',
grok: 'bg-zinc-800 text-white hover:bg-zinc-900 active:bg-black dark:bg-zinc-700 dark:hover:bg-zinc-600',
composite: 'bg-cyan-700 text-white hover:bg-cyan-800 active:bg-cyan-900 dark:bg-cyan-600 dark:hover:bg-cyan-500',
}
const BUTTON_DEFAULT = 'bg-primary-500 text-white hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-500'
@@ -83,6 +90,7 @@ const DISCOUNT: Record<Platform, string> = {
antigravity: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300',
gemini: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
grok: 'bg-zinc-100 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200',
composite: 'bg-cyan-100 text-cyan-800 dark:bg-cyan-900/40 dark:text-cyan-300',
}
const DISCOUNT_DEFAULT = 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300'
@@ -93,6 +101,7 @@ const GRADIENT: Record<Platform, string> = {
antigravity: 'from-purple-500 to-purple-600',
gemini: 'from-blue-500 to-blue-600',
grok: 'from-zinc-700 to-zinc-900',
composite: 'from-slate-600 to-cyan-600',
}
const GRADIENT_DEFAULT = 'from-primary-500 to-primary-600'
@@ -103,6 +112,7 @@ const GRADIENT_TEXT: Record<Platform, string> = {
antigravity: 'text-purple-100',
gemini: 'text-blue-100',
grok: 'text-zinc-100',
composite: 'text-cyan-100',
}
const GRADIENT_TEXT_DEFAULT = 'text-primary-100'
@@ -112,13 +122,14 @@ const GRADIENT_SUBTEXT: Record<Platform, string> = {
antigravity: 'text-purple-200',
gemini: 'text-blue-200',
grok: 'text-zinc-300',
composite: 'text-cyan-200',
}
const GRADIENT_SUBTEXT_DEFAULT = 'text-primary-200'
// ── Public API ──────────────────────────────────────────────────────
function isPlatform(p: string): p is Platform {
return p === 'anthropic' || p === 'openai' || p === 'antigravity' || p === 'gemini' || p === 'grok'
return p === 'anthropic' || p === 'openai' || p === 'antigravity' || p === 'gemini' || p === 'grok' || p === 'composite'
}
export function platformBadgeClass(p: string): string {
@@ -172,6 +183,7 @@ export function platformLabel(p: string): string {
case 'antigravity': return 'Antigravity'
case 'gemini': return 'Gemini'
case 'grok': return 'Grok'
case 'composite': return 'Composite'
default: return p || 'API'
}
}
+19 -6
View File
@@ -3841,6 +3841,7 @@ const platformOptions = computed(() => [
{ value: "gemini", label: "Gemini" },
{ value: "antigravity", label: "Antigravity" },
{ value: "grok", label: "Grok" },
{ value: "composite", label: "Composite" },
]);
const platformFilterOptions = computed(() => [
@@ -3850,6 +3851,7 @@ const platformFilterOptions = computed(() => [
{ value: "gemini", label: "Gemini" },
{ value: "antigravity", label: "Antigravity" },
{ value: "grok", label: "Grok" },
{ value: "composite", label: "Composite" },
]);
const editStatusOptions = computed(() => [
@@ -3936,29 +3938,40 @@ const invalidRequestFallbackOptionsForEdit = computed(() => {
return options;
});
// 复制账号的源分组选项(创建时)- 仅包含相同平台且有账号的分组
const canCopyAccountsFromGroup = (targetPlatform: GroupPlatform, sourcePlatform: GroupPlatform) =>
targetPlatform === "composite" || sourcePlatform === targetPlatform;
const copyAccountsGroupLabel = (g: AdminGroup) => {
const count = g.account_count || 0;
const platform = t("admin.groups.platforms." + g.platform);
return `${g.name} - ${platform} (${t("admin.groups.accountsCount", { count })})`;
};
// 复制账号的源分组选项(创建时)- 相同平台;composite 分组可汇总各平台账号
const copyAccountsGroupOptions = computed(() => {
const eligibleGroups = groups.value.filter(
(g) => g.platform === createForm.platform && (g.account_count || 0) > 0,
(g) =>
canCopyAccountsFromGroup(createForm.platform, g.platform) &&
(g.account_count || 0) > 0,
);
return eligibleGroups.map((g) => ({
value: g.id,
label: `${g.name} (${t("admin.groups.accountsCount", { count: g.account_count || 0 })})`,
label: copyAccountsGroupLabel(g),
}));
});
// 复制账号的源分组选项(编辑时)- 仅包含相同平台且有账号的分组,排除自身
// 复制账号的源分组选项(编辑时)- 相同平台;composite 分组可汇总各平台账号,排除自身
const copyAccountsGroupOptionsForEdit = computed(() => {
const currentId = editingGroup.value?.id;
const eligibleGroups = groups.value.filter(
(g) =>
g.platform === editForm.platform &&
canCopyAccountsFromGroup(editForm.platform, g.platform) &&
(g.account_count || 0) > 0 &&
g.id !== currentId,
);
return eligibleGroups.map((g) => ({
value: g.id,
label: `${g.name} (${t("admin.groups.accountsCount", { count: g.account_count || 0 })})`,
label: copyAccountsGroupLabel(g),
}));
});