Преглед на файлове

fix: 修复内存泄漏问题

1. stores/taskQueue.ts: 将任务数组限制从100提升到200,并优化 autoCleanupTasks 逻辑,保留所有活跃任务的同时限制总任务数

2. views/Accounts/index.vue: 移除对 taskStore.tasks 的 deep watch,改用 taskStore.accountRefreshTrigger 触发器监听账号刷新,避免每次任务变化都触发全量比较

3. stores/tabs.ts:
   - closeTab 时清理对应的 tabRefreshTrigger
   - 添加每5分钟定期清理无效 trigger 的定时器
ethanfly преди 3 дни
родител
ревизия
ae77b7cab6
променени са 3 файла, в които са добавени 57 реда и са изтрити 39 реда
  1. 23 3
      client/src/stores/tabs.ts
  2. 33 16
      client/src/stores/taskQueue.ts
  3. 1 20
      client/src/views/Accounts/index.vue

+ 23 - 3
client/src/stores/tabs.ts

@@ -197,12 +197,17 @@ export const useTabsStore = defineStore('tabs', () => {
   function closeTab(id: string) {
     const index = tabs.value.findIndex(tab => tab.id === id);
     if (index === -1) return;
-    
+
     const tab = tabs.value[index];
     if (!tab.closable) return;
-    
+
     tabs.value.splice(index, 1);
-    
+
+    // 清理该 tab 的 refresh trigger
+    if (tabRefreshTrigger.value[id]) {
+      delete tabRefreshTrigger.value[id];
+    }
+
     // 如果关闭的是当前激活的标签页,激活相邻的标签页
     if (activeTabId.value === id) {
       if (tabs.value.length > 0) {
@@ -292,6 +297,21 @@ export const useTabsStore = defineStore('tabs', () => {
     return computed(() => tabRefreshTrigger.value[tabId] || 0);
   }
 
+  // 每5分钟清理一次过期的 trigger(当 tab 已关闭但 trigger 仍存在时)
+  const triggerCleanupTimer = setInterval(() => {
+    const activeIds = new Set(tabs.value.map(t => t.id));
+    let cleaned = 0;
+    Object.keys(tabRefreshTrigger.value).forEach(id => {
+      if (!activeIds.has(id)) {
+        delete tabRefreshTrigger.value[id];
+        cleaned++;
+      }
+    });
+    if (cleaned > 0) {
+      console.log(`[Tabs] Cleaned up ${cleaned} stale tab refresh triggers`);
+    }
+  }, 5 * 60 * 1000);
+
   return {
     // 状态
     tabs,

+ 33 - 16
client/src/stores/taskQueue.ts

@@ -8,6 +8,9 @@ import { useServerStore } from './server';
 import request from '@/api/request';
 
 export const useTaskQueueStore = defineStore('taskQueue', () => {
+  // 最多保留200条任务,超过删除最旧的已完成/失败/取消任务
+  const MAX_TASKS = 200;
+
   // 状态
   const tasks = ref<Task[]>([]);
   const isDialogVisible = ref(false);
@@ -537,26 +540,40 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
     rebuildTaskIndex();
   }
   
-  // 自动清理老旧任务(保留最近100个已完成任务
+  // 自动清理老旧任务(限制总任务数不超过 MAX_TASKS
   function autoCleanupTasks() {
-    const completedTasks = tasks.value.filter(t => 
+    if (tasks.value.length <= MAX_TASKS) return;
+
+    // 分离活跃任务和已完成/失败/取消的任务
+    const activeTasks = tasks.value.filter(t =>
+      t.status === 'pending' || t.status === 'running'
+    );
+    const oldTasks = tasks.value.filter(t =>
       t.status === 'completed' || t.status === 'failed' || t.status === 'cancelled'
     );
-    
-    // 如果已完成任务超过100个,只保留最新的100个
-    if (completedTasks.length > 100) {
-      const sortedCompleted = completedTasks.sort((a, b) => 
-        new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
-      );
-      
-      const toKeep = sortedCompleted.slice(0, 100);
-      const activeTasks = tasks.value.filter(t => 
-        t.status === 'pending' || t.status === 'running'
-      );
-      
-      tasks.value = [...activeTasks, ...toKeep];
-      console.log(`[TaskQueue] Cleaned up ${completedTasks.length - 100} old completed tasks`);
+
+    // 如果活跃任务已经超过限制,保留所有活跃任务
+    if (activeTasks.length >= MAX_TASKS) {
+      tasks.value = activeTasks.slice(0, MAX_TASKS);
+      console.log(`[TaskQueue] Active tasks exceed limit, trimmed to ${MAX_TASKS}`);
+      return;
     }
+
+    // 计算需要保留多少个已完成任务
+    const slotsForCompleted = MAX_TASKS - activeTasks.length;
+
+    // 按时间排序(最新的在前)
+    const sortedOldTasks = oldTasks.sort((a, b) =>
+      new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
+    );
+
+    // 合并:所有活跃任务 + 最新的一部分已完成任务
+    tasks.value = [
+      ...activeTasks,
+      ...sortedOldTasks.slice(0, slotsForCompleted)
+    ];
+
+    console.log(`[TaskQueue] Task queue trimmed from ${tasks.value.length + (tasks.value.length - MAX_TASKS)} to ${MAX_TASKS}`);
   }
 
   return {

+ 1 - 20
client/src/views/Accounts/index.vue

@@ -377,32 +377,13 @@ import { useTabsStore } from '@/stores/tabs';
 const taskStore = useTaskQueueStore();
 const tabsStore = useTabsStore();
 
-// 监听任务列表变化,当 sync_account 任务完成时自动刷新账号列表
-watch(() => taskStore.tasks, (newTasks, oldTasks) => {
-  const hasSyncAccountComplete = newTasks.some(task => {
-    if (task.type !== 'sync_account') return false;
-    const oldTask = oldTasks?.find(t => t.id === task.id);
-    // 任务状态变为 completed 或 failed
-    if (oldTask && oldTask.status !== task.status && 
-        (task.status === 'completed' || task.status === 'failed')) {
-      return true;
-    }
-    return false;
-  });
-  
-  if (hasSyncAccountComplete) {
-    console.log('[Accounts] Sync account task completed, refreshing list...');
-    loadAccounts();
-  }
-}, { deep: true });
-
 // 监听账号刷新信号(当浏览器登录添加账号后或账号同步任务完成时触发)
 watch(() => tabsStore.accountRefreshTrigger, () => {
   console.log('[Accounts] Account refresh triggered (from tabs), reloading list...');
   loadAccounts();
 });
 
-// 监听任务队列的账号刷新信号
+// 监听任务队列的账号刷新信号(当 sync_account 任务完成时由 taskStore 触发)
 watch(() => taskStore.accountRefreshTrigger, () => {
   console.log('[Accounts] Account refresh triggered (from task), reloading list...');
   loadAccounts();