feat(pool,priority): 号池聚合显示、API 格式归一化与优先级管理重构

- 优先级管理对话框按 family 分组显示 API 格式,号池 key 聚合为单条目展示
- 拖拽排序改用 key ID 替代数组索引,号池聚合项禁用拖拽/编辑/开关操作
- 后端 key 分组查询增加 API 格式键归一化,返回 provider_id
- 提取 OAuth auth_config 解密逻辑,新增 _derive_oauth_expires_at 从加密配置派生过期时间
- 号池管理移除会话列,调整 OAuth 过期信息与刷新按钮的布局顺序
This commit is contained in:
fawney19
2026-03-04 12:59:27 +08:00
parent 095e312ab3
commit 57b86034cf
6 changed files with 694 additions and 143 deletions

View File

@@ -294,9 +294,6 @@
<TableHead class="w-24 font-semibold whitespace-nowrap">
状态
</TableHead>
<TableHead class="w-20 font-semibold text-center whitespace-nowrap">
会话
</TableHead>
<TableHead class="w-24 font-semibold whitespace-nowrap">
最后使用
</TableHead>
@@ -347,18 +344,6 @@
{{ key.auth_type === 'oauth' ? '[OAuth Token]' : (key.auth_type === 'service_account' ? '[Service Account]' : '[Key]') }}
</span>
<template v-if="key.auth_type === 'oauth'">
<span
v-if="getKeyOAuthExpires(key)"
class="text-[10px]"
:class="{
'text-destructive': getKeyOAuthExpires(key)?.isInvalid || getKeyOAuthExpires(key)?.isExpired,
'text-warning': getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isInvalid,
'text-muted-foreground': !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isInvalid
}"
:title="getOAuthStatusTitle(key)"
>
{{ getKeyOAuthExpires(key)?.text }}
</span>
<Button
variant="ghost"
size="icon"
@@ -372,6 +357,18 @@
:class="{ 'animate-spin': refreshingOAuthKeyId === key.key_id }"
/>
</Button>
<span
v-if="getKeyOAuthExpires(key)"
class="text-[10px]"
:class="{
'text-destructive': getKeyOAuthExpires(key)?.isInvalid || getKeyOAuthExpires(key)?.isExpired,
'text-warning': getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isInvalid,
'text-muted-foreground': !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isInvalid
}"
:title="getOAuthStatusTitle(key)"
>
{{ getKeyOAuthExpires(key)?.text }}
</span>
</template>
<Badge
v-if="key.oauth_plan_type"
@@ -529,11 +526,6 @@
{{ getSchedulingBadgeLabel(key) }}
</Badge>
</TableCell>
<TableCell class="py-3 text-center">
<span class="text-xs tabular-nums">
{{ formatSessionCount(key.sticky_sessions) }}
</span>
</TableCell>
<TableCell class="py-3">
<span class="text-[10px] text-muted-foreground whitespace-nowrap">
{{ key.last_used_at ? formatRelativeTime(key.last_used_at) : '-' }}
@@ -735,18 +727,6 @@
{{ key.auth_type === 'oauth' ? '[OAuth Token]' : (key.auth_type === 'service_account' ? '[Service Account]' : '[Key]') }}
</span>
<template v-if="key.auth_type === 'oauth'">
<span
v-if="getKeyOAuthExpires(key)"
class="text-[10px]"
:class="{
'text-destructive': getKeyOAuthExpires(key)?.isInvalid || getKeyOAuthExpires(key)?.isExpired,
'text-warning': getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isInvalid,
'text-muted-foreground': !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isInvalid
}"
:title="getOAuthStatusTitle(key)"
>
{{ getKeyOAuthExpires(key)?.text }}
</span>
<Button
variant="ghost"
size="icon"
@@ -760,6 +740,18 @@
:class="{ 'animate-spin': refreshingOAuthKeyId === key.key_id }"
/>
</Button>
<span
v-if="getKeyOAuthExpires(key)"
class="text-[10px]"
:class="{
'text-destructive': getKeyOAuthExpires(key)?.isInvalid || getKeyOAuthExpires(key)?.isExpired,
'text-warning': getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isInvalid,
'text-muted-foreground': !getKeyOAuthExpires(key)?.isExpired && !getKeyOAuthExpires(key)?.isExpiringSoon && !getKeyOAuthExpires(key)?.isInvalid
}"
:title="getOAuthStatusTitle(key)"
>
{{ getKeyOAuthExpires(key)?.text }}
</span>
</template>
<Badge
v-if="key.oauth_plan_type"
@@ -973,16 +965,6 @@
/>
</div>
</div>
<div class="p-2 bg-muted/50 rounded-lg text-xs">
<div class="text-muted-foreground mb-0.5">
会话
</div>
<div
class="font-medium tabular-nums text-[11px]"
>
{{ formatSessionCount(key.sticky_sessions) }}
</div>
</div>
<div class="p-2 bg-muted/50 rounded-lg text-xs">
<div class="text-muted-foreground mb-0.5">
最后使用
@@ -2221,12 +2203,6 @@ function formatStatUsd(value: number | null | undefined): string {
return `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}
function formatSessionCount(value: number | null | undefined): string {
const n = Number(value ?? 0)
if (!Number.isFinite(n) || n <= 0) return '0'
return Math.round(n).toLocaleString('en-US')
}
function formatRelativeTime(isoStr: string): string {
const diff = (Date.now() - new Date(isoStr).getTime()) / 1000
if (diff < 60) return '刚刚'