mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-21 23:53:57 +08:00
Limit long-lived page and component retention while virtualizing large card views to keep runtime memory lower. Defer heavy editor, chart, workflow, calendar, and icon code so the app loads less JavaScript up front.
340 lines
8.3 KiB
TypeScript
340 lines
8.3 KiB
TypeScript
import { onMounted, onUnmounted, ref, type Ref } from 'vue'
|
||
import { sseManagerSingleton } from '@/utils/sseManager'
|
||
import { addBackgroundTimer, removeBackgroundTimer } from '@/utils/backgroundManager'
|
||
|
||
/**
|
||
* 后台优化组合函数
|
||
* 统一管理SSE连接和定时器,优化iOS后台性能
|
||
*/
|
||
export function useBackgroundOptimization() {
|
||
/**
|
||
* 使用优化的SSE连接
|
||
* @param url SSE连接地址
|
||
* @param messageHandler 消息处理函数
|
||
* @param listenerId 监听器ID(用于区分不同的监听器)
|
||
* @param options 选项
|
||
*/
|
||
const useSSE = (
|
||
url: string,
|
||
messageHandler: (event: MessageEvent) => void,
|
||
listenerId: string,
|
||
options?: {
|
||
backgroundCloseDelay?: number
|
||
reconnectDelay?: number
|
||
maxReconnectAttempts?: number
|
||
connectDelay?: number // 新增:连接延迟
|
||
},
|
||
) => {
|
||
// 使用独立的SSE管理器,确保每个监听器都有独立的连接
|
||
const manager = sseManagerSingleton.getIndependentManager(url, listenerId, options)
|
||
const isConnected = ref(false)
|
||
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
||
|
||
const cleanup = () => {
|
||
if (connectTimer) {
|
||
clearTimeout(connectTimer)
|
||
connectTimer = null
|
||
}
|
||
|
||
manager.removeMessageListener(listenerId)
|
||
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
||
isConnected.value = false
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 延迟建立连接,确保组件完全挂载
|
||
const connectDelay = options?.connectDelay || 100
|
||
connectTimer = setTimeout(() => {
|
||
connectTimer = null
|
||
try {
|
||
manager.addMessageListener(listenerId, event => {
|
||
messageHandler(event)
|
||
isConnected.value = true
|
||
})
|
||
} catch (error) {
|
||
console.error('SSE连接建立失败:', error)
|
||
}
|
||
}, connectDelay)
|
||
})
|
||
|
||
onUnmounted(cleanup)
|
||
|
||
return {
|
||
manager,
|
||
readyState: () => manager.readyState,
|
||
close: cleanup,
|
||
isConnected,
|
||
forceReconnect: () => manager.forceReconnect(),
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用优化的定时器
|
||
* @param id 定时器ID
|
||
* @param callback 回调函数
|
||
* @param interval 间隔时间(毫秒)
|
||
* @param options 选项
|
||
*/
|
||
const useTimer = (
|
||
id: string,
|
||
callback: () => void,
|
||
interval: number,
|
||
options?: {
|
||
runInBackground?: boolean
|
||
skipInitialRun?: boolean
|
||
},
|
||
) => {
|
||
onMounted(() => {
|
||
addBackgroundTimer(id, callback, interval, options)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
removeBackgroundTimer(id)
|
||
})
|
||
|
||
return {
|
||
remove: () => removeBackgroundTimer(id),
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用延迟SSE连接(类似原来的setTimeout延迟)
|
||
* @param url SSE连接地址
|
||
* @param messageHandler 消息处理函数
|
||
* @param listenerId 监听器ID
|
||
* @param delay 延迟时间(毫秒)
|
||
* @param options SSE选项
|
||
*/
|
||
const useDelayedSSE = (
|
||
url: string,
|
||
messageHandler: (event: MessageEvent) => void,
|
||
listenerId: string,
|
||
delay: number = 3000,
|
||
options?: Parameters<typeof useSSE>[3],
|
||
) => {
|
||
// 使用独立的SSE管理器,确保每个监听器都有独立的连接
|
||
const manager = sseManagerSingleton.getIndependentManager(url, listenerId, options)
|
||
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
||
|
||
const cleanup = () => {
|
||
if (connectTimer) {
|
||
clearTimeout(connectTimer)
|
||
connectTimer = null
|
||
}
|
||
|
||
manager.removeMessageListener(listenerId)
|
||
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
||
}
|
||
|
||
onMounted(() => {
|
||
connectTimer = setTimeout(() => {
|
||
connectTimer = null
|
||
manager.addMessageListener(listenerId, messageHandler)
|
||
}, delay)
|
||
})
|
||
|
||
onUnmounted(cleanup)
|
||
|
||
return {
|
||
manager,
|
||
readyState: () => manager.readyState,
|
||
close: cleanup,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用进度SSE连接(用于进度监听)
|
||
* @param url SSE连接地址
|
||
* @param messageHandler 消息处理函数
|
||
* @param listenerId 监听器ID
|
||
* @param isActive 是否激活的响应式变量
|
||
*/
|
||
const useProgressSSE = (
|
||
url: string,
|
||
messageHandler: (event: MessageEvent) => void,
|
||
listenerId: string,
|
||
isActive: Ref<boolean>,
|
||
) => {
|
||
const getManager = () =>
|
||
sseManagerSingleton.getIndependentManager(url, listenerId, {
|
||
backgroundCloseDelay: 1000, // 进度SSE更快关闭
|
||
reconnectDelay: 1000,
|
||
maxReconnectAttempts: 5,
|
||
})
|
||
|
||
let manager: ReturnType<typeof getManager> | null = null
|
||
let isListening = false
|
||
|
||
const startProgress = () => {
|
||
if (!isActive.value || isListening) return
|
||
|
||
manager ??= getManager()
|
||
manager.addMessageListener(listenerId, messageHandler)
|
||
isListening = true
|
||
}
|
||
|
||
const stopProgress = (destroyManager = true) => {
|
||
if (!manager) {
|
||
isListening = false
|
||
return
|
||
}
|
||
|
||
manager.removeMessageListener(listenerId)
|
||
|
||
if (destroyManager) {
|
||
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
||
manager = null
|
||
}
|
||
|
||
isListening = false
|
||
}
|
||
|
||
onUnmounted(() => {
|
||
stopProgress(true)
|
||
})
|
||
|
||
return {
|
||
start: startProgress,
|
||
stop: stopProgress,
|
||
get manager() {
|
||
return manager
|
||
},
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用数据刷新定时器(用于仪表盘等数据刷新)
|
||
* @param id 定时器ID
|
||
* @param loadDataFunc 加载数据函数
|
||
* @param interval 刷新间隔(毫秒)
|
||
* @param immediate 是否立即执行
|
||
*/
|
||
const useDataRefresh = (
|
||
id: string,
|
||
loadDataFunc: () => Promise<void> | void,
|
||
interval: number = 3000,
|
||
immediate: boolean = true,
|
||
) => {
|
||
const loading = ref(false)
|
||
|
||
const wrappedLoadData = async () => {
|
||
if (loading.value) return
|
||
|
||
loading.value = true
|
||
try {
|
||
await loadDataFunc()
|
||
} catch (error) {
|
||
console.error(`数据刷新失败 [${id}]:`, error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
if (immediate) {
|
||
await wrappedLoadData()
|
||
}
|
||
|
||
addBackgroundTimer(id, wrappedLoadData, interval, {
|
||
runInBackground: false, // 后台不刷新数据
|
||
skipInitialRun: true, // 已经手动执行过了
|
||
})
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
removeBackgroundTimer(id)
|
||
})
|
||
|
||
return {
|
||
loading,
|
||
refresh: wrappedLoadData,
|
||
stop: () => removeBackgroundTimer(id),
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用条件性数据刷新定时器(用于需要动态启停的场景)
|
||
* @param id 定时器ID
|
||
* @param loadDataFunc 加载数据函数
|
||
* @param condition 条件响应式引用,为true时启动定时器
|
||
* @param interval 刷新间隔(毫秒)
|
||
* @param immediate 是否立即执行
|
||
*/
|
||
const useConditionalDataRefresh = (
|
||
id: string,
|
||
loadDataFunc: () => Promise<void> | void,
|
||
condition: Ref<boolean>,
|
||
interval: number = 3000,
|
||
immediate: boolean = true,
|
||
) => {
|
||
const loading = ref(false)
|
||
const isTimerActive = ref(false)
|
||
|
||
const wrappedLoadData = async () => {
|
||
if (loading.value || !condition.value) return
|
||
|
||
loading.value = true
|
||
try {
|
||
await loadDataFunc()
|
||
} catch (error) {
|
||
console.error(`条件数据刷新失败 [${id}]:`, error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const startTimer = () => {
|
||
if (!isTimerActive.value && condition.value) {
|
||
addBackgroundTimer(id, wrappedLoadData, interval, {
|
||
runInBackground: false,
|
||
skipInitialRun: !immediate,
|
||
})
|
||
isTimerActive.value = true
|
||
}
|
||
}
|
||
|
||
const stopTimer = () => {
|
||
if (isTimerActive.value) {
|
||
removeBackgroundTimer(id)
|
||
isTimerActive.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (condition.value) {
|
||
startTimer()
|
||
}
|
||
|
||
// 监听条件变化
|
||
watch(condition, (newValue: boolean) => {
|
||
if (newValue) {
|
||
startTimer()
|
||
} else {
|
||
stopTimer()
|
||
}
|
||
})
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
stopTimer()
|
||
})
|
||
|
||
return {
|
||
loading,
|
||
refresh: wrappedLoadData,
|
||
stop: stopTimer,
|
||
start: startTimer,
|
||
isActive: isTimerActive,
|
||
}
|
||
}
|
||
|
||
return {
|
||
useSSE,
|
||
useTimer,
|
||
useDelayedSSE,
|
||
useProgressSSE,
|
||
useDataRefresh,
|
||
useConditionalDataRefresh,
|
||
}
|
||
}
|