perf: 全栈查询优化、前端缓存去重与页面可见性优化

后端:
- SQL count 查询统一改用 func.count() 子查询替代 query.count()
- Dashboard/Audit 等页面多次独立查询合并为单次聚合查询
- Provider summary 列表改为批量查询消除 N+1 问题
- DailyStats 逐天循环查询改为 CASE 分桶单次查询
- 使用 load_only() 减少不必要的列加载
- cache_decorator 支持嵌套属性路径解析(dotted vary_by)
- 多个管理/公共端点新增 @cache_result 缓存装饰

前端:
- cache.ts 新增 in-flight 请求复用、dedupedRequest、buildCacheKey
- 大量 API 调用添加前端缓存或去重
- 多个页面定时器在标签页隐藏时暂停、可见时恢复
- Auth 检查从 setInterval 改为 storage + visibilitychange 事件驱动
- 请求竞态防护(requestId 模式)

数据库:
- Usage 表新增 idx_usage_status_user_created 复合索引
This commit is contained in:
fawney19
2026-03-03 22:04:40 +08:00
parent 0a60492146
commit 97b0146ce9
66 changed files with 2306 additions and 657 deletions
+43 -11
View File
@@ -911,6 +911,8 @@ const showDetail = ref(false)
const selectedTask = ref<AsyncTaskDetail | null>(null)
const detailAutoRefresh = ref(false)
let detailRefreshInterval: ReturnType<typeof setInterval> | null = null
const isPageVisible = ref(typeof document === 'undefined' ? true : !document.hidden)
let overviewRefreshInFlight = false
// 使用记录详情抽屉状态
const usageDetailOpen = ref(false)
@@ -953,6 +955,16 @@ async function fetchStats() {
}
}
async function refreshOverview() {
if (overviewRefreshInFlight) return
overviewRefreshInFlight = true
try {
await Promise.all([fetchTasks(), fetchStats()])
} finally {
overviewRefreshInFlight = false
}
}
// 打开任务详情
async function openTaskDetail(task: AsyncTaskItem) {
try {
@@ -993,22 +1005,27 @@ function toggleDetailAutoRefresh() {
// 开始详情自动刷新
function startDetailAutoRefresh() {
if (!isPageVisible.value) return
if (detailRefreshInterval) return
// 立即刷新一次
refreshTaskDetail()
detailRefreshInterval = setInterval(() => {
if (selectedTask.value && showDetail.value) {
if (isPageVisible.value && selectedTask.value && showDetail.value) {
refreshTaskDetail()
}
}, 5000)
}
// 停止详情自动刷新
function stopDetailAutoRefresh() {
function pauseDetailAutoRefresh() {
if (detailRefreshInterval) {
clearInterval(detailRefreshInterval)
detailRefreshInterval = null
}
}
// 停止详情自动刷新
function stopDetailAutoRefresh() {
pauseDetailAutoRefresh()
detailAutoRefresh.value = false
}
@@ -1052,8 +1069,7 @@ async function cancelTask(task: AsyncTaskItem | AsyncTaskDetail) {
toast({
title: '任务已取消',
})
fetchTasks()
fetchStats()
await refreshOverview()
if (showDetail.value) {
closeDetail()
}
@@ -1222,11 +1238,11 @@ let autoRefreshInterval: ReturnType<typeof setInterval> | null = null
const AUTO_REFRESH_INTERVAL = 5000 // 5秒
function startAutoRefresh() {
if (!isPageVisible.value) return
if (autoRefreshInterval) return
autoRefreshInterval = setInterval(() => {
if (hasProcessingTasks.value && !loading.value) {
fetchTasks()
fetchStats()
if (isPageVisible.value && hasProcessingTasks.value && !loading.value) {
refreshOverview()
}
}, AUTO_REFRESH_INTERVAL)
}
@@ -1240,19 +1256,35 @@ function stopAutoRefresh() {
// 监听是否有进行中的任务,动态启停自动刷新
watch(hasProcessingTasks, (has) => {
if (has) {
if (has && isPageVisible.value) {
startAutoRefresh()
} else {
stopAutoRefresh()
}
}, { immediate: true })
function handleVisibilityChange() {
isPageVisible.value = !document.hidden
if (!isPageVisible.value) {
stopAutoRefresh()
pauseDetailAutoRefresh()
return
}
if (hasProcessingTasks.value) {
startAutoRefresh()
}
if (detailAutoRefresh.value && selectedTask.value && showDetail.value) {
startDetailAutoRefresh()
}
}
onMounted(() => {
fetchTasks()
fetchStats()
document.addEventListener('visibilitychange', handleVisibilityChange)
refreshOverview()
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
stopAutoRefresh()
stopDetailAutoRefresh()
clearTimeout(filterTimeout)