mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
feat: 优化SSE连接延迟,添加初始化状态提示
This commit is contained in:
@@ -127,7 +127,7 @@ const progressSSE = useProgressSSE(
|
|||||||
`${import.meta.env.VITE_API_BASE_URL}system/progress/filetransfer`,
|
`${import.meta.env.VITE_API_BASE_URL}system/progress/filetransfer`,
|
||||||
handleProgressMessage,
|
handleProgressMessage,
|
||||||
'transfer-queue-progress',
|
'transfer-queue-progress',
|
||||||
progressActive
|
progressActive,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 使用SSE监听加载进度
|
// 使用SSE监听加载进度
|
||||||
@@ -166,6 +166,7 @@ onUnmounted(() => {
|
|||||||
:value="progressValue"
|
:value="progressValue"
|
||||||
color="primary"
|
color="primary"
|
||||||
indeterminate
|
indeterminate
|
||||||
|
:height="2"
|
||||||
/>
|
/>
|
||||||
<VCardItem v-if="dataList.length > 0 && progressValue > 0" class="text-center pt-2">
|
<VCardItem v-if="dataList.length > 0 && progressValue > 0" class="text-center pt-2">
|
||||||
<span class="text-sm">{{ progressText }}</span>
|
<span class="text-sm">{{ progressText }}</span>
|
||||||
|
|||||||
@@ -22,22 +22,38 @@ export function useBackgroundOptimization() {
|
|||||||
backgroundCloseDelay?: number
|
backgroundCloseDelay?: number
|
||||||
reconnectDelay?: number
|
reconnectDelay?: number
|
||||||
maxReconnectAttempts?: number
|
maxReconnectAttempts?: number
|
||||||
|
connectDelay?: number // 新增:连接延迟
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const manager = sseManagerSingleton.getManager(url, options)
|
const manager = sseManagerSingleton.getManager(url, options)
|
||||||
|
const isConnected = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
manager.addMessageListener(listenerId, messageHandler)
|
// 延迟建立连接,确保组件完全挂载
|
||||||
|
const connectDelay = options?.connectDelay || 100
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
manager.addMessageListener(listenerId, event => {
|
||||||
|
messageHandler(event)
|
||||||
|
isConnected.value = true
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('SSE连接建立失败:', error)
|
||||||
|
}
|
||||||
|
}, connectDelay)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
manager.removeMessageListener(listenerId)
|
manager.removeMessageListener(listenerId)
|
||||||
|
isConnected.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
manager,
|
manager,
|
||||||
readyState: () => manager.readyState,
|
readyState: () => manager.readyState,
|
||||||
close: () => manager.removeMessageListener(listenerId),
|
close: () => manager.removeMessageListener(listenerId),
|
||||||
|
isConnected,
|
||||||
|
forceReconnect: () => manager.forceReconnect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1079,6 +1079,7 @@ export default {
|
|||||||
program: 'Program',
|
program: 'Program',
|
||||||
content: 'Content',
|
content: 'Content',
|
||||||
refreshing: 'Refreshing',
|
refreshing: 'Refreshing',
|
||||||
|
initializing: 'Initializing',
|
||||||
},
|
},
|
||||||
moduleTest: {
|
moduleTest: {
|
||||||
normal: 'Normal',
|
normal: 'Normal',
|
||||||
|
|||||||
@@ -1075,6 +1075,7 @@ export default {
|
|||||||
program: '程序',
|
program: '程序',
|
||||||
content: '内容',
|
content: '内容',
|
||||||
refreshing: '正在刷新',
|
refreshing: '正在刷新',
|
||||||
|
initializing: '正在初始化',
|
||||||
},
|
},
|
||||||
moduleTest: {
|
moduleTest: {
|
||||||
normal: '正常',
|
normal: '正常',
|
||||||
|
|||||||
@@ -1074,6 +1074,7 @@ export default {
|
|||||||
program: '程序',
|
program: '程序',
|
||||||
content: '內容',
|
content: '內容',
|
||||||
refreshing: '正在刷新',
|
refreshing: '正在刷新',
|
||||||
|
initializing: '正在初始化',
|
||||||
},
|
},
|
||||||
moduleTest: {
|
moduleTest: {
|
||||||
normal: '正常',
|
normal: '正常',
|
||||||
|
|||||||
+63
-13
@@ -14,6 +14,8 @@ export class SSEManager {
|
|||||||
reconnectDelay: number
|
reconnectDelay: number
|
||||||
maxReconnectAttempts: number
|
maxReconnectAttempts: number
|
||||||
}
|
}
|
||||||
|
private reconnectAttempts = 0
|
||||||
|
private isConnecting = false
|
||||||
|
|
||||||
constructor(url: string, options: Partial<typeof SSEManager.prototype.options> = {}) {
|
constructor(url: string, options: Partial<typeof SSEManager.prototype.options> = {}) {
|
||||||
this.url = url
|
this.url = url
|
||||||
@@ -21,7 +23,7 @@ export class SSEManager {
|
|||||||
backgroundCloseDelay: 5000, // 5秒后关闭后台连接
|
backgroundCloseDelay: 5000, // 5秒后关闭后台连接
|
||||||
reconnectDelay: 3000, // 3秒后重连
|
reconnectDelay: 3000, // 3秒后重连
|
||||||
maxReconnectAttempts: 3,
|
maxReconnectAttempts: 3,
|
||||||
...options
|
...options,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setupVisibilityListener()
|
this.setupVisibilityListener()
|
||||||
@@ -52,7 +54,6 @@ export class SSEManager {
|
|||||||
|
|
||||||
this.backgroundCloseTimer = window.setTimeout(() => {
|
this.backgroundCloseTimer = window.setTimeout(() => {
|
||||||
if (this.isBackground && this.eventSource) {
|
if (this.isBackground && this.eventSource) {
|
||||||
console.log('SSE: 后台关闭连接')
|
|
||||||
this.eventSource.close()
|
this.eventSource.close()
|
||||||
this.eventSource = null
|
this.eventSource = null
|
||||||
}
|
}
|
||||||
@@ -70,26 +71,32 @@ export class SSEManager {
|
|||||||
|
|
||||||
// 立即重新建立连接
|
// 立即重新建立连接
|
||||||
if (!this.eventSource || this.eventSource.readyState === EventSource.CLOSED) {
|
if (!this.eventSource || this.eventSource.readyState === EventSource.CLOSED) {
|
||||||
console.log('SSE: 前台恢复连接')
|
|
||||||
this.reconnectSSE()
|
this.reconnectSSE()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private reconnectSSE(attemptCount = 0) {
|
private reconnectSSE(attemptCount = 0) {
|
||||||
if (attemptCount >= this.options.maxReconnectAttempts) {
|
if (attemptCount >= this.options.maxReconnectAttempts) {
|
||||||
console.warn('SSE: 达到最大重连次数')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isConnecting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConnecting = true
|
||||||
|
this.reconnectAttempts = attemptCount
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.eventSource = new EventSource(this.url)
|
this.eventSource = new EventSource(this.url)
|
||||||
|
|
||||||
this.eventSource.onopen = () => {
|
this.eventSource.onopen = () => {
|
||||||
console.log('SSE: 连接已建立')
|
this.isConnecting = false
|
||||||
|
this.reconnectAttempts = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSource.onerror = (error) => {
|
this.eventSource.onerror = error => {
|
||||||
console.error('SSE: 连接错误', error)
|
this.isConnecting = false
|
||||||
|
|
||||||
if (this.eventSource?.readyState === EventSource.CLOSED) {
|
if (this.eventSource?.readyState === EventSource.CLOSED) {
|
||||||
// 连接已关闭,尝试重连
|
// 连接已关闭,尝试重连
|
||||||
@@ -99,13 +106,13 @@ export class SSEManager {
|
|||||||
|
|
||||||
this.reconnectTimer = window.setTimeout(() => {
|
this.reconnectTimer = window.setTimeout(() => {
|
||||||
if (!this.isBackground) {
|
if (!this.isBackground) {
|
||||||
this.reconnectSSE(attemptCount + 1)
|
this.reconnectSSE(this.reconnectAttempts + 1)
|
||||||
}
|
}
|
||||||
}, this.options.reconnectDelay)
|
}, this.options.reconnectDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSource.onmessage = (event) => {
|
this.eventSource.onmessage = event => {
|
||||||
// 分发消息给所有监听器
|
// 分发消息给所有监听器
|
||||||
this.listeners.forEach(listener => {
|
this.listeners.forEach(listener => {
|
||||||
try {
|
try {
|
||||||
@@ -115,9 +122,19 @@ export class SSEManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('SSE: 创建连接失败', error)
|
this.isConnecting = false
|
||||||
|
|
||||||
|
// 连接创建失败,尝试重连
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
clearTimeout(this.reconnectTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reconnectTimer = window.setTimeout(() => {
|
||||||
|
if (!this.isBackground) {
|
||||||
|
this.reconnectSSE(this.reconnectAttempts + 1)
|
||||||
|
}
|
||||||
|
}, this.options.reconnectDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +144,8 @@ export class SSEManager {
|
|||||||
addMessageListener(id: string, listener: (event: MessageEvent) => void) {
|
addMessageListener(id: string, listener: (event: MessageEvent) => void) {
|
||||||
this.listeners.set(id, listener)
|
this.listeners.set(id, listener)
|
||||||
|
|
||||||
// 如果还没有连接,现在建立连接
|
// 如果还没有连接且不在后台,现在建立连接
|
||||||
if (!this.eventSource && !this.isBackground) {
|
if (!this.eventSource && !this.isBackground && !this.isConnecting) {
|
||||||
this.reconnectSSE()
|
this.reconnectSSE()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,6 +182,8 @@ export class SSEManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.listeners.clear()
|
this.listeners.clear()
|
||||||
|
this.isConnecting = false
|
||||||
|
this.reconnectAttempts = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,6 +199,37 @@ export class SSEManager {
|
|||||||
get connectionUrl(): string {
|
get connectionUrl(): string {
|
||||||
return this.url
|
return this.url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制重新连接
|
||||||
|
*/
|
||||||
|
forceReconnect() {
|
||||||
|
this.close()
|
||||||
|
if (!this.isBackground) {
|
||||||
|
this.reconnectSSE()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否有活跃的监听器
|
||||||
|
*/
|
||||||
|
get hasActiveListeners(): boolean {
|
||||||
|
return this.listeners.size > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前重连次数
|
||||||
|
*/
|
||||||
|
get currentReconnectAttempts(): number {
|
||||||
|
return this.reconnectAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否达到最大重连次数
|
||||||
|
*/
|
||||||
|
get hasReachedMaxAttempts(): boolean {
|
||||||
|
return this.reconnectAttempts >= this.options.maxReconnectAttempts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { isToday } from '@/@core/utils/index'
|
import { isToday } from '@/@core/utils/index'
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
||||||
|
|
||||||
// 定义输入变量
|
// 定义输入变量
|
||||||
@@ -16,6 +16,9 @@ const { useSSE } = useBackgroundOptimization()
|
|||||||
// 已解析的日志列表
|
// 已解析的日志列表
|
||||||
const parsedLogs = ref<{ level: string; date: string; time: string; program: string; content: string }[]>([])
|
const parsedLogs = ref<{ level: string; date: string; time: string; program: string; content: string }[]>([])
|
||||||
|
|
||||||
|
// 组件是否已挂载
|
||||||
|
const isMounted = ref(false)
|
||||||
|
|
||||||
// 表头
|
// 表头
|
||||||
const headers = [
|
const headers = [
|
||||||
{ title: t('logging.level'), value: 'level' },
|
{ title: t('logging.level'), value: 'level' },
|
||||||
@@ -72,23 +75,40 @@ function handleSSEMessage(event: MessageEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的SSE连接
|
// 使用优化的SSE连接,添加延迟确保弹窗完全打开
|
||||||
useSSE(
|
const { manager, isConnected } = useSSE(
|
||||||
`${import.meta.env.VITE_API_BASE_URL}system/logging?logfile=${
|
`${import.meta.env.VITE_API_BASE_URL}system/logging?logfile=${encodeURIComponent(props.logfile) ?? 'moviepilot.log'}`,
|
||||||
encodeURIComponent(props.logfile) ?? 'moviepilot.log'
|
|
||||||
}`,
|
|
||||||
handleSSEMessage,
|
handleSSEMessage,
|
||||||
`logging-${props.logfile}`,
|
`logging-${props.logfile}`,
|
||||||
{
|
{
|
||||||
backgroundCloseDelay: 5000,
|
backgroundCloseDelay: 5000,
|
||||||
reconnectDelay: 3000,
|
reconnectDelay: 3000,
|
||||||
maxReconnectAttempts: 3
|
maxReconnectAttempts: 3,
|
||||||
}
|
connectDelay: 300, // 延迟300ms建立连接,确保弹窗完全打开
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 监听弹窗状态变化,确保弹窗完全打开后再建立连接
|
||||||
|
onMounted(() => {
|
||||||
|
// 延迟标记组件已挂载,确保弹窗完全渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
isMounted.value = true
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听连接状态变化
|
||||||
|
watch(isConnected, connected => {})
|
||||||
|
|
||||||
|
// 监听日志数据变化
|
||||||
|
watch(parsedLogs, logs => {}, { deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LoadingBanner v-if="parsedLogs.length === 0" class="mt-12" :text="t('logging.refreshing') + ' ...'" />
|
<LoadingBanner
|
||||||
|
v-if="!isMounted || !isConnected || parsedLogs.length === 0"
|
||||||
|
class="mt-12"
|
||||||
|
:text="!isMounted ? t('logging.initializing') + ' ...' : t('logging.refreshing') + ' ...'"
|
||||||
|
/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<VTable class="table-rounded" hide-default-footer disable-sort>
|
<VTable class="table-rounded" hide-default-footer disable-sort>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -104,8 +124,14 @@ useSSE(
|
|||||||
<VChip size="small" :color="getLogColor(item.level)" variant="elevated" v-text="item.level" />
|
<VChip size="small" :color="getLogColor(item.level)" variant="elevated" v-text="item.level" />
|
||||||
</template>
|
</template>
|
||||||
<template #item.time="{ item }">
|
<template #item.time="{ item }">
|
||||||
<span class="text-sm">{{ isToday(dayjs(item.date).toDate()) ? item.time : `${item.date}
|
<span class="text-sm">
|
||||||
${item.time}` }}</span>
|
{{
|
||||||
|
isToday(dayjs(item.date).toDate())
|
||||||
|
? item.time
|
||||||
|
: `${item.date}
|
||||||
|
${item.time}`
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #item.program="{ item }">
|
<template #item.program="{ item }">
|
||||||
<h6 class="text-sm font-weight-medium">{{ item.program }}</h6>
|
<h6 class="text-sm font-weight-medium">{{ item.program }}</h6>
|
||||||
|
|||||||
Reference in New Issue
Block a user