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
@@ -657,7 +657,7 @@
</template>
<script setup lang="ts">
import { ref, watch, computed, onBeforeUnmount } from 'vue'
import { ref, watch, computed, onMounted, onBeforeUnmount } from 'vue'
import Button from '@/components/ui/button.vue'
import { useEscapeKey } from '@/composables/useEscapeKey'
import { useClipboard } from '@/composables/useClipboard'
@@ -716,10 +716,13 @@ const historicalPricing = ref<{
} | null>(null)
const autoRefreshTimer = ref<ReturnType<typeof setInterval> | null>(null)
const autoRefreshing = ref(false)
const isPageVisible = ref(typeof document === 'undefined' ? true : !document.hidden)
const curlCopying = ref(false)
const curlCopied = ref(false)
const replayDialogOpen = ref(false)
const AUTO_REFRESH_INTERVAL_MS = 1000
let loadDetailRequestId = 0
let loadDetailInFlight = false
// 监听标签页切换
watch(activeTab, (newTab) => {
@@ -1097,13 +1100,20 @@ watch(() => props.isOpen, async (isOpen) => {
})
async function loadDetail(id: string, silent = false) {
if (silent && loadDetailInFlight) {
return
}
const requestId = ++loadDetailRequestId
loadDetailInFlight = true
if (!silent) {
loading.value = true
historicalPricing.value = null
}
error.value = null
try {
detail.value = await dashboardApi.getRequestDetail(id)
const response = await dashboardApi.getRequestDetail(id)
if (requestId !== loadDetailRequestId) return
detail.value = response
// 首次加载时选择默认 tab
if (!silent) {
@@ -1145,14 +1155,18 @@ async function loadDetail(id: string, silent = false) {
}
}
} catch (err) {
if (requestId !== loadDetailRequestId) return
log.error('Failed to load request detail:', err)
if (!silent) {
error.value = '加载请求详情失败'
}
} finally {
if (!silent) {
if (!silent && requestId === loadDetailRequestId) {
loading.value = false
}
if (requestId === loadDetailRequestId) {
loadDetailInFlight = false
}
}
}
@@ -1175,12 +1189,17 @@ function stopAutoRefresh() {
}
function startAutoRefresh() {
if (autoRefreshTimer.value || !props.requestId || !props.isOpen) {
if (autoRefreshTimer.value) {
autoRefreshing.value = true
return
}
if (!isPageVisible.value || !props.requestId || !props.isOpen) {
autoRefreshing.value = false
return
}
autoRefreshing.value = true
autoRefreshTimer.value = setInterval(async () => {
if (!props.requestId || !props.isOpen) {
if (!isPageVisible.value || !props.requestId || !props.isOpen) {
stopAutoRefresh()
return
}
@@ -1218,8 +1237,26 @@ async function refreshDetail() {
startAutoRefresh()
}
function handleVisibilityChange() {
isPageVisible.value = !document.hidden
if (!isPageVisible.value) {
stopAutoRefresh()
return
}
if (props.isOpen && props.requestId && !isRequestCompleted()) {
startAutoRefresh()
}
}
onMounted(() => {
document.addEventListener('visibilitychange', handleVisibilityChange)
})
onBeforeUnmount(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
stopAutoRefresh()
loadDetailRequestId += 1
loadDetailInFlight = false
})
function formatDateTime(dateStr: string | null | undefined): string {