perf: 并行化 admin 聚合路由并完善前端缓存预取

- gateway: usage detail / provider summary / pool overview / users list 改为 tokio join 并行拉取依赖数据
- usage: interval timeline 支持自动刷新并按查询区间动态展示,取消服务端 120 分钟过滤并在 ScatterChart 统一封顶
- frontend: 新增管理端导航预取工具及 SidebarNav/MainLayout 触发,admin 读接口统一走 cachedRequest 的短期缓存
- dashboard: request detail 支持短 TTL 缓存并在 UsageRecordsTable mousedown 时预取
- data: migrate 测试在 wait_for_postgres 失败时清理子进程,避免遗留
This commit is contained in:
fawney19
2026-04-19 15:17:25 +08:00
parent 97cd877ce5
commit 41b51f10a9
30 changed files with 619 additions and 243 deletions

View File

@@ -66,14 +66,18 @@ const props = withDefaults(defineProps<{
title: string
isAdmin: boolean
hours?: number
refreshIntervalMs?: number
}>(), {
hours: 24 // 默认当天
hours: 24, // 默认当天
refreshIntervalMs: 30000
})
const loading = ref(false)
const timelineData = ref<IntervalTimelineResponse | null>(null)
const primaryColor = ref('201, 100, 66') // 默认主题色
let loadRequestId = 0
let refreshTimer: ReturnType<typeof setTimeout> | null = null
const isPageVisible = ref(typeof document === 'undefined' ? true : !document.hidden)
const ADMIN_TIMELINE_LIMIT = 1500
const USER_TIMELINE_LIMIT = 1200
@@ -90,7 +94,11 @@ function getPrimaryColor(): string {
onMounted(() => {
primaryColor.value = getPrimaryColor()
if (typeof document !== 'undefined') {
document.addEventListener('visibilitychange', handleVisibilityChange)
}
void loadData()
scheduleNextRefresh()
})
// 预定义的颜色列表(用于区分不同用户/模型)
@@ -315,11 +323,45 @@ async function loadData() {
}
}
function stopRefreshTimer() {
if (refreshTimer) {
clearTimeout(refreshTimer)
refreshTimer = null
}
}
function scheduleNextRefresh() {
if (refreshTimer) return
if (!isPageVisible.value) return
if (!props.refreshIntervalMs || props.refreshIntervalMs <= 0) return
refreshTimer = setTimeout(async () => {
refreshTimer = null
await loadData()
scheduleNextRefresh()
}, props.refreshIntervalMs)
}
function handleVisibilityChange() {
isPageVisible.value = !document.hidden
if (!isPageVisible.value) {
stopRefreshTimer()
return
}
void loadData()
scheduleNextRefresh()
}
watch([() => props.hours, () => props.isAdmin], () => {
void loadData()
stopRefreshTimer()
scheduleNextRefresh()
})
onBeforeUnmount(() => {
loadRequestId++
if (typeof document !== 'undefined') {
document.removeEventListener('visibilitychange', handleVisibilityChange)
}
stopRefreshTimer()
})
</script>

View File

@@ -1664,7 +1664,10 @@ async function loadDetail(id: string, silent = false) {
}
error.value = null
try {
const response = await dashboardApi.getRequestDetail(id, { includeBodies: false })
const response = await dashboardApi.getRequestDetail(id, {
includeBodies: false,
cacheTtlMs: silent ? 0 : 5_000
})
if (requestId !== loadDetailRequestId) return
const previousDetail = detail.value

View File

@@ -341,7 +341,7 @@
v-else
:key="record.id"
:class="isAdmin ? 'cursor-pointer border-b border-border/40 hover:bg-muted/30 transition-colors h-[72px]' : 'border-b border-border/40 hover:bg-muted/30 transition-colors h-[72px]'"
@mousedown="handleMouseDown"
@mousedown="handleRowMouseDown($event, record.id)"
@click="handleRowClick($event, record.id)"
>
<TableCell class="text-xs py-4 w-[70px]">
@@ -730,6 +730,7 @@ const emit = defineEmits<{
'update:autoRefresh': [value: boolean]
'refresh': []
'showDetail': [id: string]
'prefetchDetail': [id: string]
}>()
// 静态常量(放在 defineProps/defineEmits 之后)
@@ -776,6 +777,13 @@ watch(localSearch, (value) => {
// 使用复用的行点击逻辑
const { handleMouseDown, shouldTriggerRowClick } = useRowClick()
function handleRowMouseDown(event: MouseEvent, id: string) {
handleMouseDown(event)
if (!props.isAdmin) return
if (event.button !== 0) return
emit('prefetchDetail', id)
}
// 处理行点击,排除文本选择操作
function handleRowClick(event: MouseEvent, id: string) {
if (!props.isAdmin) return