From c2eee380918d33b686cb64db528b4835963cf536 Mon Sep 17 00:00:00 2001 From: duorameng <2997944583@qq.com> Date: Tue, 30 Jun 2026 11:59:58 +0800 Subject: [PATCH] perf(web): optimize API polling frequency and respect page visibility --- web/src/components/NodeSwitcher.vue | 15 +++++++++++- web/src/components/SystemNotice.vue | 20 ++++++++++++++- web/src/views/agents/Agents.vue | 38 +++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/web/src/components/NodeSwitcher.vue b/web/src/components/NodeSwitcher.vue index 7f038fa..3f0c570 100644 --- a/web/src/components/NodeSwitcher.vue +++ b/web/src/components/NodeSwitcher.vue @@ -37,8 +37,9 @@ function handleNodeChange(val: any) { function startPolling() { stopPolling() + if (document.visibilityState === 'hidden') return loadNodes() - timer = setInterval(loadNodes, 30000) + timer = setInterval(loadNodes, 120000) // 放宽至 2 分钟一次 } function stopPolling() { @@ -48,6 +49,16 @@ function stopPolling() { } } +function handleVisibilityChange() { + if (document.visibilityState === 'visible') { + if (isMaster.value) { + startPolling() + } + } else { + stopPolling() + } +} + async function checkRoleAndInit() { try { const role = await api.settings.get('interconnect', 'interconnect_role') @@ -86,10 +97,12 @@ roleBus.on((newRole) => { onMounted(async () => { await checkRoleAndInit() + document.addEventListener('visibilitychange', handleVisibilityChange) }) onUnmounted(() => { stopPolling() + document.removeEventListener('visibilitychange', handleVisibilityChange) }) diff --git a/web/src/components/SystemNotice.vue b/web/src/components/SystemNotice.vue index 8237d30..823eaac 100644 --- a/web/src/components/SystemNotice.vue +++ b/web/src/components/SystemNotice.vue @@ -9,6 +9,9 @@ import { api, type AppLog, LOG_CATEGORY, LOG_STATUS } from '@/api' import { toast } from 'vue-sonner' import { format } from 'date-fns' +import { eventBus } from '@/utils/event-bus' +import { LOG_EVENTS } from '@/constants' + const notices = ref([]) const unreadCount = ref(0) const loading = ref(false) @@ -56,12 +59,27 @@ async function markAllAsRead() { } let timer: number +let unsubscribe: () => void + onMounted(() => { fetchNotices() - timer = window.setInterval(fetchNotices, 60000) // 每分钟拉取一次 + + // 订阅 WebSocket 实时事件,在收到新系统日志通知时主动刷新 + unsubscribe = eventBus.subscribe((msg) => { + if (msg.type === LOG_EVENTS.ADDED) { + const log = msg.payload + if (log && log.category === LOG_CATEGORY.SYSTEM_NOTICE) { + fetchNotices() + } + } + }) + + // 10 分钟一次的长轮询作为断连兜底 + timer = window.setInterval(fetchNotices, 10 * 60 * 1000) }) onUnmounted(() => { + if (unsubscribe) unsubscribe() if (timer) clearInterval(timer) }) diff --git a/web/src/views/agents/Agents.vue b/web/src/views/agents/Agents.vue index af38692..fc70a31 100644 --- a/web/src/views/agents/Agents.vue +++ b/web/src/views/agents/Agents.vue @@ -64,6 +64,16 @@ async function loadAgents() { } } +async function refreshAgentsOnly() { + if (document.visibilityState === 'hidden') return + try { + const agentList = await api.agents.list() + agents.value = agentList + } catch (e) { + console.error('Failed to poll agent status', e) + } +} + // Handler functions for Agent function viewDetail(agent: Agent) { agentDetailDialogRef.value?.openDialog(agent) @@ -103,13 +113,37 @@ function openEditToken(token: AgentToken) { editTokenDialogRef.value?.openEdit(token) } +function startPolling() { + stopPolling() + if (document.visibilityState === 'hidden') return + refreshTimer = setInterval(refreshAgentsOnly, 30000) // 仅轮询 Agent 在线状态,频率放宽至 30s +} + +function stopPolling() { + if (refreshTimer) { + clearInterval(refreshTimer) + refreshTimer = null + } +} + +function handleVisibilityChange() { + if (document.visibilityState === 'visible') { + refreshAgentsOnly() + startPolling() + } else { + stopPolling() + } +} + onMounted(() => { loadAgents() - refreshTimer = setInterval(loadAgents, 10000) + startPolling() + document.addEventListener('visibilitychange', handleVisibilityChange) }) onUnmounted(() => { - if (refreshTimer) clearInterval(refreshTimer) + stopPolling() + document.removeEventListener('visibilitychange', handleVisibilityChange) })