mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-15 00:40:43 +08:00
引入全局 SSE 单例,统一 onopen/onerror,任务列表与日历共用连接,切页不丢事件
- 进入日历页即时 tick 与本地补拉,消除 60s 等待 - 校正可见性回退判断为 appSSE;清理重复 SSE 回调设置 - 保留原轮询兜底与可见性恢复逻辑,确保 SSE 异常时行为一致
This commit is contained in:
parent
2d944600e6
commit
080c73fc07
@ -3237,6 +3237,15 @@
|
|||||||
// 任务列表自动检测更新相关
|
// 任务列表自动检测更新相关
|
||||||
tasklistAutoWatchTimer: null,
|
tasklistAutoWatchTimer: null,
|
||||||
tasklistLatestFilesSignature: '',
|
tasklistLatestFilesSignature: '',
|
||||||
|
// 全局 SSE 单例与监听标志
|
||||||
|
appSSE: null,
|
||||||
|
appSSEInitialized: false,
|
||||||
|
calendarSSEListenerAdded: false,
|
||||||
|
tasklistSSEListenerAdded: false,
|
||||||
|
// 存放已绑定的事件处理器,便于避免重复绑定
|
||||||
|
onCalendarChangedHandler: null,
|
||||||
|
onTasklistChangedHandler: null,
|
||||||
|
// 兼容旧字段(不再使用独立 SSE 实例)
|
||||||
tasklistSSE: null,
|
tasklistSSE: null,
|
||||||
calendarSSE: null,
|
calendarSSE: null,
|
||||||
// 创建任务相关数据
|
// 创建任务相关数据
|
||||||
@ -3576,6 +3585,8 @@
|
|||||||
activeTab(newValue, oldValue) {
|
activeTab(newValue, oldValue) {
|
||||||
// 如果切换到任务列表页面,则刷新任务最新信息和元数据
|
// 如果切换到任务列表页面,则刷新任务最新信息和元数据
|
||||||
if (newValue === 'tasklist') {
|
if (newValue === 'tasklist') {
|
||||||
|
// 确保全局 SSE 已建立
|
||||||
|
try { this.ensureGlobalSSE(); } catch (e) {}
|
||||||
this.loadTaskLatestInfo();
|
this.loadTaskLatestInfo();
|
||||||
this.loadTasklistMetadata();
|
this.loadTasklistMetadata();
|
||||||
// 启动任务列表的后台监听
|
// 启动任务列表的后台监听
|
||||||
@ -3586,6 +3597,8 @@
|
|||||||
}
|
}
|
||||||
// 切换到追剧日历:立刻检查一次并启动后台监听;离开则停止监听
|
// 切换到追剧日历:立刻检查一次并启动后台监听;离开则停止监听
|
||||||
if (newValue === 'calendar') {
|
if (newValue === 'calendar') {
|
||||||
|
// 确保全局 SSE 已建立
|
||||||
|
try { this.ensureGlobalSSE(); } catch (e) {}
|
||||||
// 立即检查一次(若已初始化过监听,直接调用tick引用)
|
// 立即检查一次(若已初始化过监听,直接调用tick引用)
|
||||||
// 先本地读取一次,立刻应用“已转存”状态(不依赖轮询)
|
// 先本地读取一次,立刻应用“已转存”状态(不依赖轮询)
|
||||||
try { this.loadCalendarEpisodesLocal && this.loadCalendarEpisodesLocal(); } catch (e) {}
|
try { this.loadCalendarEpisodesLocal && this.loadCalendarEpisodesLocal(); } catch (e) {}
|
||||||
@ -3633,6 +3646,9 @@
|
|||||||
this.fetchUserInfo(); // 获取用户信息
|
this.fetchUserInfo(); // 获取用户信息
|
||||||
this.fetchAccountsDetail(); // 获取账号详细信息
|
this.fetchAccountsDetail(); // 获取账号详细信息
|
||||||
|
|
||||||
|
// 应用级别:在挂载时确保全局 SSE 建立一次
|
||||||
|
try { this.ensureGlobalSSE(); } catch (e) {}
|
||||||
|
|
||||||
// 迁移旧的localStorage数据到新格式(为每个账号单独存储目录)
|
// 迁移旧的localStorage数据到新格式(为每个账号单独存储目录)
|
||||||
this.migrateFileManagerFolderData();
|
this.migrateFileManagerFolderData();
|
||||||
|
|
||||||
@ -3822,6 +3838,80 @@
|
|||||||
this.stopCalendarAutoWatch();
|
this.stopCalendarAutoWatch();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 启动任务列表轮询兜底(仅在未运行时启动)
|
||||||
|
startTasklistPollingFallback() {
|
||||||
|
try {
|
||||||
|
if (!this.tasklistAutoWatchTimer) {
|
||||||
|
this.tasklistAutoWatchTimer = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get('/task_latest_info');
|
||||||
|
if (res.data && res.data.success) {
|
||||||
|
const latestFiles = res.data.data.latest_files || {};
|
||||||
|
const sig = this.calcLatestFilesSignature(latestFiles);
|
||||||
|
if (sig && sig !== this.tasklistLatestFilesSignature) {
|
||||||
|
// 更新签名,触发热更新
|
||||||
|
this.tasklistLatestFilesSignature = sig;
|
||||||
|
this.taskLatestFiles = latestFiles;
|
||||||
|
// 重新加载任务元数据,确保海报和元数据能热更新
|
||||||
|
await this.loadTasklistMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('任务列表后台监听检查失败:', e);
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
// 确保全局 SSE 单例存在(仅建立一次)
|
||||||
|
ensureGlobalSSE() {
|
||||||
|
try {
|
||||||
|
if (this.appSSEInitialized && this.appSSE) return;
|
||||||
|
if (!this.appSSE) {
|
||||||
|
this.appSSE = new EventSource('/api/calendar/stream');
|
||||||
|
}
|
||||||
|
this.appSSEInitialized = true;
|
||||||
|
// 统一 onopen:SSE 成功后停止两侧轮询
|
||||||
|
this.appSSE.onopen = () => {
|
||||||
|
try {
|
||||||
|
if (this.calendarAutoWatchTimer) {
|
||||||
|
clearInterval(this.calendarAutoWatchTimer);
|
||||||
|
this.calendarAutoWatchTimer = null;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
if (this.tasklistAutoWatchTimer) {
|
||||||
|
clearInterval(this.tasklistAutoWatchTimer);
|
||||||
|
this.tasklistAutoWatchTimer = null;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
// 统一 onerror:关闭SSE并回退双侧轮询
|
||||||
|
this.appSSE.onerror = () => {
|
||||||
|
try { this.appSSE.close(); } catch (e) {}
|
||||||
|
this.appSSE = null;
|
||||||
|
this.appSSEInitialized = false;
|
||||||
|
// 日历回退:若没有轮询定时器,则恢复轮询并立即执行一次
|
||||||
|
try {
|
||||||
|
if (!this.calendarAutoWatchTimer && this.calendarAutoWatchTickRef) {
|
||||||
|
const baseIntervalMs = this.calendar && this.calendar.manageMode ? 5 * 1000 : 60 * 1000;
|
||||||
|
this.calendarAutoWatchTimer = setInterval(this.calendarAutoWatchTickRef, baseIntervalMs);
|
||||||
|
this.calendarAutoWatchTickRef();
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
// 任务列表回退:若没有轮询定时器,则恢复轮询
|
||||||
|
try {
|
||||||
|
if (!this.tasklistAutoWatchTimer) {
|
||||||
|
this.startTasklistPollingFallback();
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
// ping 心跳占位
|
||||||
|
try { this.appSSE.addEventListener('ping', () => {}); } catch (e) {}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略失败,后续可回退轮询
|
||||||
|
}
|
||||||
|
},
|
||||||
// 任务列表海报标题(悬停:#编号 任务名称 · 状态)
|
// 任务列表海报标题(悬停:#编号 任务名称 · 状态)
|
||||||
getTasklistPosterTitle(task, index) {
|
getTasklistPosterTitle(task, index) {
|
||||||
try {
|
try {
|
||||||
@ -5612,7 +5702,7 @@
|
|||||||
this.calendarAutoWatchTimer = null;
|
this.calendarAutoWatchTimer = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this.calendarSSE && !this.calendarAutoWatchTimer && this.calendarAutoWatchTickRef) {
|
if (!this.appSSE && !this.calendarAutoWatchTimer && this.calendarAutoWatchTickRef) {
|
||||||
const baseIntervalMs = this.calendar && this.calendar.manageMode ? 5 * 1000 : 60 * 1000;
|
const baseIntervalMs = this.calendar && this.calendar.manageMode ? 5 * 1000 : 60 * 1000;
|
||||||
this.calendarAutoWatchTimer = setInterval(this.calendarAutoWatchTickRef, baseIntervalMs);
|
this.calendarAutoWatchTimer = setInterval(this.calendarAutoWatchTickRef, baseIntervalMs);
|
||||||
this.calendarAutoWatchTickRef();
|
this.calendarAutoWatchTickRef();
|
||||||
@ -5624,19 +5714,10 @@
|
|||||||
window.addEventListener('focus', this.calendarAutoWatchFocusHandler);
|
window.addEventListener('focus', this.calendarAutoWatchFocusHandler);
|
||||||
document.addEventListener('visibilitychange', this.calendarAutoWatchVisibilityHandler);
|
document.addEventListener('visibilitychange', this.calendarAutoWatchVisibilityHandler);
|
||||||
|
|
||||||
// 建立 SSE 连接,实时感知日历数据库变化(成功建立后停用轮询,失败时回退轮询)
|
// 建立/复用全局 SSE 连接(成功建立后停用轮询,失败时回退轮询)
|
||||||
try {
|
try {
|
||||||
if (!this.calendarSSE) {
|
this.ensureGlobalSSE();
|
||||||
this.calendarSSE = new EventSource('/api/calendar/stream');
|
if (this.appSSE && !this.calendarSSEListenerAdded) {
|
||||||
// SSE 打开后,停止轮询
|
|
||||||
this.calendarSSE.onopen = () => {
|
|
||||||
try {
|
|
||||||
if (this.calendarAutoWatchTimer) {
|
|
||||||
clearInterval(this.calendarAutoWatchTimer);
|
|
||||||
this.calendarAutoWatchTimer = null;
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
const onChanged = async (ev) => {
|
const onChanged = async (ev) => {
|
||||||
try {
|
try {
|
||||||
// 解析变更原因(后端通过 SSE data 传递)
|
// 解析变更原因(后端通过 SSE data 传递)
|
||||||
@ -5717,21 +5798,10 @@
|
|||||||
try { await this.loadTodayUpdatesLocal(); } catch (e) {}
|
try { await this.loadTodayUpdatesLocal(); } catch (e) {}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
this.calendarSSE.addEventListener('calendar_changed', onChanged);
|
this.onCalendarChangedHandler = onChanged;
|
||||||
// 初次连接会收到一次 ping,不做处理即可
|
this.appSSE.addEventListener('calendar_changed', onChanged);
|
||||||
this.calendarSSE.addEventListener('ping', () => {});
|
this.calendarSSEListenerAdded = true;
|
||||||
this.calendarSSE.onerror = () => {
|
// onopen/onerror 已集中在 ensureGlobalSSE,无需重复设置
|
||||||
try { this.calendarSSE.close(); } catch (e) {}
|
|
||||||
this.calendarSSE = null;
|
|
||||||
// 回退:若没有轮询定时器,则恢复轮询
|
|
||||||
try {
|
|
||||||
if (!this.calendarAutoWatchTimer && this.calendarAutoWatchTickRef) {
|
|
||||||
const baseIntervalMs = this.calendar && this.calendar.manageMode ? 5 * 1000 : 60 * 1000;
|
|
||||||
this.calendarAutoWatchTimer = setInterval(this.calendarAutoWatchTickRef, baseIntervalMs);
|
|
||||||
this.calendarAutoWatchTickRef();
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略 SSE 失败,继续使用轮询
|
// 忽略 SSE 失败,继续使用轮询
|
||||||
@ -5756,10 +5826,7 @@
|
|||||||
document.removeEventListener('visibilitychange', this.calendarAutoWatchVisibilityHandler);
|
document.removeEventListener('visibilitychange', this.calendarAutoWatchVisibilityHandler);
|
||||||
this.calendarAutoWatchVisibilityHandler = null;
|
this.calendarAutoWatchVisibilityHandler = null;
|
||||||
}
|
}
|
||||||
if (this.calendarSSE) {
|
// 不再关闭全局 SSE;仅移除本地监听(如有需要)
|
||||||
try { this.calendarSSE.close(); } catch (e) {}
|
|
||||||
this.calendarSSE = null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -9351,17 +9418,9 @@
|
|||||||
|
|
||||||
// 建立 SSE 连接,实时感知任务列表变化(成功建立后停用轮询,失败时回退轮询)
|
// 建立 SSE 连接,实时感知任务列表变化(成功建立后停用轮询,失败时回退轮询)
|
||||||
try {
|
try {
|
||||||
if (!this.tasklistSSE) {
|
// 使用全局 SSE 单例
|
||||||
this.tasklistSSE = new EventSource('/api/calendar/stream');
|
this.ensureGlobalSSE();
|
||||||
// SSE 打开后,停止轮询
|
if (this.appSSE && !this.tasklistSSEListenerAdded) {
|
||||||
this.tasklistSSE.onopen = () => {
|
|
||||||
try {
|
|
||||||
if (this.tasklistAutoWatchTimer) {
|
|
||||||
clearInterval(this.tasklistAutoWatchTimer);
|
|
||||||
this.tasklistAutoWatchTimer = null;
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
const onTasklistChanged = async (ev) => {
|
const onTasklistChanged = async (ev) => {
|
||||||
try {
|
try {
|
||||||
// 解析变更原因(后端通过 SSE data 传递)
|
// 解析变更原因(后端通过 SSE data 传递)
|
||||||
@ -9406,37 +9465,10 @@
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
this.tasklistSSE.addEventListener('calendar_changed', onTasklistChanged);
|
this.onTasklistChangedHandler = onTasklistChanged;
|
||||||
// 初次连接会收到一次 ping,不做处理即可
|
this.appSSE.addEventListener('calendar_changed', onTasklistChanged);
|
||||||
this.tasklistSSE.addEventListener('ping', () => {});
|
this.tasklistSSEListenerAdded = true;
|
||||||
this.tasklistSSE.onerror = () => {
|
// onopen/onerror 已集中在 ensureGlobalSSE,无需重复设置
|
||||||
try { this.tasklistSSE.close(); } catch (e) {}
|
|
||||||
this.tasklistSSE = null;
|
|
||||||
// 回退:若没有轮询定时器,则恢复轮询
|
|
||||||
try {
|
|
||||||
if (!this.tasklistAutoWatchTimer) {
|
|
||||||
this.tasklistAutoWatchTimer = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const res = await axios.get('/task_latest_info');
|
|
||||||
if (res.data && res.data.success) {
|
|
||||||
const latestFiles = res.data.data.latest_files || {};
|
|
||||||
const sig = this.calcLatestFilesSignature(latestFiles);
|
|
||||||
if (sig && sig !== this.tasklistLatestFilesSignature) {
|
|
||||||
// 更新签名,触发热更新
|
|
||||||
this.tasklistLatestFilesSignature = sig;
|
|
||||||
this.taskLatestFiles = latestFiles;
|
|
||||||
|
|
||||||
// 重新加载任务元数据,确保海报和元数据能热更新
|
|
||||||
await this.loadTasklistMetadata();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('任务列表后台监听检查失败:', e);
|
|
||||||
}
|
|
||||||
}, 60000);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略 SSE 失败,继续使用轮询
|
// 忽略 SSE 失败,继续使用轮询
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user