mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-07 08:46:40 +08:00
refactor: replace useBackgroundOptimization with unified useBackground composable and update Nginx SSE route configuration
This commit is contained in:
+1
-1
@@ -49,7 +49,7 @@ http {
|
|||||||
root html;
|
root html;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/v1/system/(message|progress/) {
|
location ~ ^/api/v1/(system/(message|progress/|logging)|search/.*/stream$) {
|
||||||
# SSE MIME类型设置
|
# SSE MIME类型设置
|
||||||
default_type text/event-stream;
|
default_type text/event-stream;
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import { cloneDeep } from 'lodash-es'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { downloaderDict, storageAttributes } from '@/api/constants'
|
import { downloaderDict, storageAttributes } from '@/api/constants'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|
||||||
// 获取i18n实例
|
// 获取i18n实例
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useConditionalDataRefresh } = useBackgroundOptimization()
|
const { useConditionalDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 定义输入
|
// 定义输入
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
TransferDirectoryConf,
|
TransferDirectoryConf,
|
||||||
TransferForm,
|
TransferForm,
|
||||||
} from '@/api/types'
|
} from '@/api/types'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
import MediaIdSelector from '../misc/MediaIdSelector.vue'
|
import MediaIdSelector from '../misc/MediaIdSelector.vue'
|
||||||
import ProgressDialog from './ProgressDialog.vue'
|
import ProgressDialog from './ProgressDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
@@ -24,7 +24,7 @@ import { useGlobalSettingsStore } from '@/stores'
|
|||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useProgressSSE } = useBackgroundOptimization()
|
const { useProgressSSE } = useBackground()
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import api from '@/api'
|
|||||||
import { FileItem, TransferQueue } from '@/api/types'
|
import { FileItem, TransferQueue } from '@/api/types'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
import CryptoJS from 'crypto-js'
|
import CryptoJS from 'crypto-js'
|
||||||
|
|
||||||
type TransferTask = TransferQueue['tasks'][number]
|
type TransferTask = TransferQueue['tasks'][number]
|
||||||
@@ -20,7 +20,7 @@ interface MediaTaskGroup {
|
|||||||
|
|
||||||
// 多语言支持
|
// 多语言支持
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useProgressSSE } = useBackgroundOptimization()
|
const { useProgressSSE } = useBackground()
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import ProgressDialog from '../dialog/ProgressDialog.vue'
|
|||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import MediaInfoDialog from '../dialog/MediaInfoDialog.vue'
|
import MediaInfoDialog from '../dialog/MediaInfoDialog.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
import { usePWA } from '@/composables/usePWA'
|
import { usePWA } from '@/composables/usePWA'
|
||||||
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
||||||
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useProgressSSE } = useBackgroundOptimization()
|
const { useProgressSSE } = useBackground()
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
@@ -652,7 +652,7 @@ function handleProgressMessage(event: MessageEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的进度SSE连接
|
// 使用进度SSE连接
|
||||||
const progressSSE = useProgressSSE(
|
const progressSSE = useProgressSSE(
|
||||||
`${import.meta.env.VITE_API_BASE_URL}system/progress/batchrename`,
|
`${import.meta.env.VITE_API_BASE_URL}system/progress/batchrename`,
|
||||||
handleProgressMessage,
|
handleProgressMessage,
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { onMounted, onUnmounted, ref, type Ref } from 'vue'
|
import { getCurrentInstance, onMounted, onUnmounted, ref, type Ref } from 'vue'
|
||||||
import { sseManagerSingleton } from '@/utils/sseManager'
|
import { sseManagerSingleton, type SSEManagerOptions } from '@/utils/sseManager'
|
||||||
import { addBackgroundTimer, removeBackgroundTimer } from '@/utils/backgroundManager'
|
import { addBackgroundTimer, removeBackgroundTimer } from '@/utils/backgroundManager'
|
||||||
|
|
||||||
|
type UseSSEOptions = Partial<SSEManagerOptions> & {
|
||||||
|
connectDelay?: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后台优化组合函数
|
* 后台任务组合函数
|
||||||
* 统一管理SSE连接和定时器,优化iOS后台性能
|
* 统一管理SSE连接和定时器,减少后台常驻活动。
|
||||||
*/
|
*/
|
||||||
export function useBackgroundOptimization() {
|
export function useBackground() {
|
||||||
/**
|
/**
|
||||||
* 使用优化的SSE连接
|
* 使用SSE连接
|
||||||
* @param url SSE连接地址
|
* @param url SSE连接地址
|
||||||
* @param messageHandler 消息处理函数
|
* @param messageHandler 消息处理函数
|
||||||
* @param listenerId 监听器ID(用于区分不同的监听器)
|
* @param listenerId 监听器ID(用于区分不同的监听器)
|
||||||
@@ -18,24 +22,30 @@ export function useBackgroundOptimization() {
|
|||||||
url: string,
|
url: string,
|
||||||
messageHandler: (event: MessageEvent) => void,
|
messageHandler: (event: MessageEvent) => void,
|
||||||
listenerId: string,
|
listenerId: string,
|
||||||
options?: {
|
options?: UseSSEOptions,
|
||||||
backgroundCloseDelay?: number
|
|
||||||
reconnectDelay?: number
|
|
||||||
maxReconnectAttempts?: number
|
|
||||||
connectDelay?: number // 新增:连接延迟
|
|
||||||
},
|
|
||||||
) => {
|
) => {
|
||||||
// 使用独立的SSE管理器,确保每个监听器都有独立的连接
|
// 使用独立的SSE管理器,确保每个监听器都有独立的连接
|
||||||
const manager = sseManagerSingleton.getIndependentManager(url, listenerId, options)
|
const manager = sseManagerSingleton.getIndependentManager(url, listenerId, options)
|
||||||
const isConnected = ref(false)
|
const isConnected = ref(false)
|
||||||
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let isClosed = false
|
||||||
|
const statusListenerId = `${listenerId}:status`
|
||||||
|
|
||||||
|
manager.addStatusListener(statusListenerId, status => {
|
||||||
|
isConnected.value = status === 'open'
|
||||||
|
})
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
|
if (isClosed) return
|
||||||
|
|
||||||
|
isClosed = true
|
||||||
|
|
||||||
if (connectTimer) {
|
if (connectTimer) {
|
||||||
clearTimeout(connectTimer)
|
clearTimeout(connectTimer)
|
||||||
connectTimer = null
|
connectTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manager.removeStatusListener(statusListenerId)
|
||||||
manager.removeMessageListener(listenerId)
|
manager.removeMessageListener(listenerId)
|
||||||
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
||||||
isConnected.value = false
|
isConnected.value = false
|
||||||
@@ -46,11 +56,10 @@ export function useBackgroundOptimization() {
|
|||||||
const connectDelay = options?.connectDelay || 100
|
const connectDelay = options?.connectDelay || 100
|
||||||
connectTimer = setTimeout(() => {
|
connectTimer = setTimeout(() => {
|
||||||
connectTimer = null
|
connectTimer = null
|
||||||
|
if (isClosed) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
manager.addMessageListener(listenerId, event => {
|
manager.addMessageListener(listenerId, messageHandler)
|
||||||
messageHandler(event)
|
|
||||||
isConnected.value = true
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('SSE连接建立失败:', error)
|
console.error('SSE连接建立失败:', error)
|
||||||
}
|
}
|
||||||
@@ -69,7 +78,7 @@ export function useBackgroundOptimization() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用优化的定时器
|
* 使用定时器
|
||||||
* @param id 定时器ID
|
* @param id 定时器ID
|
||||||
* @param callback 回调函数
|
* @param callback 回调函数
|
||||||
* @param interval 间隔时间(毫秒)
|
* @param interval 间隔时间(毫秒)
|
||||||
@@ -110,25 +119,40 @@ export function useBackgroundOptimization() {
|
|||||||
messageHandler: (event: MessageEvent) => void,
|
messageHandler: (event: MessageEvent) => void,
|
||||||
listenerId: string,
|
listenerId: string,
|
||||||
delay: number = 3000,
|
delay: number = 3000,
|
||||||
options?: Parameters<typeof useSSE>[3],
|
options?: UseSSEOptions,
|
||||||
) => {
|
) => {
|
||||||
// 使用独立的SSE管理器,确保每个监听器都有独立的连接
|
// 使用独立的SSE管理器,确保每个监听器都有独立的连接
|
||||||
const manager = sseManagerSingleton.getIndependentManager(url, listenerId, options)
|
const manager = sseManagerSingleton.getIndependentManager(url, listenerId, options)
|
||||||
|
const isConnected = ref(false)
|
||||||
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let isClosed = false
|
||||||
|
const statusListenerId = `${listenerId}:status`
|
||||||
|
|
||||||
|
manager.addStatusListener(statusListenerId, status => {
|
||||||
|
isConnected.value = status === 'open'
|
||||||
|
})
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
|
if (isClosed) return
|
||||||
|
|
||||||
|
isClosed = true
|
||||||
|
|
||||||
if (connectTimer) {
|
if (connectTimer) {
|
||||||
clearTimeout(connectTimer)
|
clearTimeout(connectTimer)
|
||||||
connectTimer = null
|
connectTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manager.removeStatusListener(statusListenerId)
|
||||||
manager.removeMessageListener(listenerId)
|
manager.removeMessageListener(listenerId)
|
||||||
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
sseManagerSingleton.closeIndependentManager(url, listenerId)
|
||||||
|
isConnected.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
connectTimer = setTimeout(() => {
|
connectTimer = setTimeout(() => {
|
||||||
connectTimer = null
|
connectTimer = null
|
||||||
|
if (isClosed) return
|
||||||
|
|
||||||
manager.addMessageListener(listenerId, messageHandler)
|
manager.addMessageListener(listenerId, messageHandler)
|
||||||
}, delay)
|
}, delay)
|
||||||
})
|
})
|
||||||
@@ -139,6 +163,7 @@ export function useBackgroundOptimization() {
|
|||||||
manager,
|
manager,
|
||||||
readyState: () => manager.readyState,
|
readyState: () => manager.readyState,
|
||||||
close: cleanup,
|
close: cleanup,
|
||||||
|
isConnected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,9 +214,12 @@ export function useBackgroundOptimization() {
|
|||||||
isListening = false
|
isListening = false
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
// 进度监听有些场景会在用户操作后动态创建;只有 setup 阶段创建时才注册自动卸载钩子。
|
||||||
stopProgress(true)
|
if (getCurrentInstance()) {
|
||||||
})
|
onUnmounted(() => {
|
||||||
|
stopProgress(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: startProgress,
|
start: startProgress,
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
import { formatDateDifference } from '@core/utils/formatters'
|
import { formatDateDifference } from '@core/utils/formatters'
|
||||||
import { SystemNotification } from '@/api/types'
|
import { SystemNotification } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDelayedSSE } = useBackgroundOptimization()
|
const { useDelayedSSE } = useBackground()
|
||||||
|
|
||||||
// 是否有新消息
|
// 是否有新消息
|
||||||
const hasNewMessage = ref(false)
|
const hasNewMessage = ref(false)
|
||||||
@@ -39,7 +39,7 @@ function handleMessage(event: MessageEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的SSE连接,延迟3秒启动,避免认证问题
|
// 延迟3秒启动SSE连接,避免认证信息尚未准备好。
|
||||||
useDelayedSSE(
|
useDelayedSSE(
|
||||||
`${import.meta.env.VITE_API_BASE_URL}system/message`,
|
`${import.meta.env.VITE_API_BASE_URL}system/message`,
|
||||||
handleMessage,
|
handleMessage,
|
||||||
|
|||||||
+56
-15
@@ -240,6 +240,8 @@ const progressEnabled = ref(false)
|
|||||||
// 进度是否激活
|
// 进度是否激活
|
||||||
const progressActive = ref(false)
|
const progressActive = ref(false)
|
||||||
|
|
||||||
|
let progressResetTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
// 是否显示搜索进度
|
// 是否显示搜索进度
|
||||||
const isSearchProgressVisible = computed(
|
const isSearchProgressVisible = computed(
|
||||||
() => progressActive.value || (!isRefreshed.value && (progressEnabled.value || progressValue.value > 0)),
|
() => progressActive.value || (!isRefreshed.value && (progressEnabled.value || progressValue.value > 0)),
|
||||||
@@ -268,10 +270,12 @@ const errorTitle = ref(t('resource.noData'))
|
|||||||
const errorDescription = ref(t('resource.noResourceFound'))
|
const errorDescription = ref(t('resource.noResourceFound'))
|
||||||
|
|
||||||
let searchEventSource: EventSource | null = null
|
let searchEventSource: EventSource | null = null
|
||||||
|
let searchStreamIdleTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
const streamPreviewLimit = 24
|
const streamPreviewLimit = 24
|
||||||
const streamUiFlushDelay = 1000
|
const streamUiFlushDelay = 1000
|
||||||
const streamPreviewBufferLimit = streamPreviewLimit * 4
|
const streamPreviewBufferLimit = streamPreviewLimit * 4
|
||||||
|
const searchStreamIdleTimeout = 90_000
|
||||||
|
|
||||||
const streamTotalCount = ref(0)
|
const streamTotalCount = ref(0)
|
||||||
const streamPreviewDataList = ref<Array<Context>>([])
|
const streamPreviewDataList = ref<Array<Context>>([])
|
||||||
@@ -343,6 +347,7 @@ const watchProgressValue = watch(
|
|||||||
|
|
||||||
// 使用SSE监听加载进度
|
// 使用SSE监听加载进度
|
||||||
function startLoadingProgress() {
|
function startLoadingProgress() {
|
||||||
|
clearProgressResetTimer()
|
||||||
watchProgressValue.resume()
|
watchProgressValue.resume()
|
||||||
progressText.value = t('resource.searching')
|
progressText.value = t('resource.searching')
|
||||||
progressValue.value = 0
|
progressValue.value = 0
|
||||||
@@ -357,18 +362,41 @@ function stopLoadingProgress() {
|
|||||||
|
|
||||||
// 确保进度显示100%,然后再渐进清零
|
// 确保进度显示100%,然后再渐进清零
|
||||||
progressValue.value = 100
|
progressValue.value = 100
|
||||||
setTimeout(() => {
|
clearProgressResetTimer()
|
||||||
|
progressResetTimer = setTimeout(() => {
|
||||||
|
progressResetTimer = null
|
||||||
progressValue.value = 0
|
progressValue.value = 0
|
||||||
progressEnabled.value = false
|
progressEnabled.value = false
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearProgressResetTimer() {
|
||||||
|
if (progressResetTimer) {
|
||||||
|
clearTimeout(progressResetTimer)
|
||||||
|
progressResetTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭SSE连接
|
// 关闭SSE连接
|
||||||
function closeSearchEventSource() {
|
function closeSearchEventSource(source?: EventSource) {
|
||||||
|
if (source && searchEventSource !== source) {
|
||||||
|
source.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (searchEventSource) {
|
if (searchEventSource) {
|
||||||
searchEventSource.close()
|
searchEventSource.close()
|
||||||
searchEventSource = null
|
searchEventSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearSearchStreamIdleTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearchStreamIdleTimer() {
|
||||||
|
if (searchStreamIdleTimer) {
|
||||||
|
clearTimeout(searchStreamIdleTimer)
|
||||||
|
searchStreamIdleTimer = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渐进式搜索期间只保留有限预览数据,避免每个批次都触发完整筛选和分组计算。
|
// 渐进式搜索期间只保留有限预览数据,避免每个批次都触发完整筛选和分组计算。
|
||||||
@@ -607,36 +635,48 @@ function searchByStream(params: SearchParams, requestToken?: string) {
|
|||||||
const source = new EventSource(buildSearchStreamUrl(params, requestToken))
|
const source = new EventSource(buildSearchStreamUrl(params, requestToken))
|
||||||
searchEventSource = source
|
searchEventSource = source
|
||||||
|
|
||||||
|
const settleSearchStream = (callback: () => void) => {
|
||||||
|
if (settled) return
|
||||||
|
|
||||||
|
settled = true
|
||||||
|
closeSearchEventSource(source)
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetIdleTimeout = () => {
|
||||||
|
clearSearchStreamIdleTimer()
|
||||||
|
searchStreamIdleTimer = setTimeout(() => {
|
||||||
|
settleSearchStream(() => reject(new Error(t('resource.noResourceFound'))))
|
||||||
|
}, searchStreamIdleTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetIdleTimeout()
|
||||||
|
|
||||||
source.onmessage = event => {
|
source.onmessage = event => {
|
||||||
|
if (source !== searchEventSource || settled) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
resetIdleTimeout()
|
||||||
const eventData = JSON.parse(event.data)
|
const eventData = JSON.parse(event.data)
|
||||||
handleSearchStreamMessage(eventData)
|
handleSearchStreamMessage(eventData)
|
||||||
|
|
||||||
if (eventData.type === 'error') {
|
if (eventData.type === 'error') {
|
||||||
settled = true
|
settleSearchStream(resolve)
|
||||||
closeSearchEventSource()
|
|
||||||
resolve()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventData.type === 'done') {
|
if (eventData.type === 'done') {
|
||||||
settled = true
|
settleSearchStream(resolve)
|
||||||
closeSearchEventSource()
|
|
||||||
resolve()
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
settled = true
|
settleSearchStream(() => reject(error))
|
||||||
closeSearchEventSource()
|
|
||||||
reject(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
source.onerror = () => {
|
source.onerror = () => {
|
||||||
if (settled) return
|
if (source !== searchEventSource || settled) return
|
||||||
|
|
||||||
settled = true
|
settleSearchStream(() => reject(new Error(t('resource.noResourceFound'))))
|
||||||
closeSearchEventSource()
|
|
||||||
reject(new Error(t('resource.noResourceFound')))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1011,6 +1051,7 @@ onMounted(async () => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
closeSearchEventSource()
|
closeSearchEventSource()
|
||||||
stopLoadingProgress()
|
stopLoadingProgress()
|
||||||
|
clearProgressResetTimer()
|
||||||
stopAiRecommendPolling()
|
stopAiRecommendPolling()
|
||||||
clearStreamPreviewState()
|
clearStreamPreviewState()
|
||||||
})
|
})
|
||||||
|
|||||||
+179
-67
@@ -1,21 +1,33 @@
|
|||||||
|
export type SSEConnectionStatus = 'idle' | 'connecting' | 'open' | 'error' | 'closed'
|
||||||
|
|
||||||
|
export interface SSEManagerOptions {
|
||||||
|
backgroundCloseDelay: number
|
||||||
|
reconnectDelay: number
|
||||||
|
maxReconnectAttempts: number
|
||||||
|
reconnectBackoffMultiplier: number
|
||||||
|
maxReconnectDelay: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSEMessageListener = (event: MessageEvent) => void
|
||||||
|
type SSEStatusListener = (status: SSEConnectionStatus) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSE连接管理器
|
* SSE连接管理器
|
||||||
* 优化后台SSE连接,减少iOS系统杀掉应用的概率
|
* 统一收口 EventSource 生命周期,避免后台常驻连接和重复重连。
|
||||||
*/
|
*/
|
||||||
export class SSEManager {
|
export class SSEManager {
|
||||||
private eventSource: EventSource | null = null
|
private eventSource: EventSource | null = null
|
||||||
private url: string
|
private url: string
|
||||||
private isBackground = false
|
private isBackground = document.hidden
|
||||||
private reconnectTimer: number | null = null
|
private reconnectTimer: number | null = null
|
||||||
private backgroundCloseTimer: number | null = null
|
private backgroundCloseTimer: number | null = null
|
||||||
private listeners: Map<string, (event: MessageEvent) => void> = new Map()
|
private listeners: Map<string, SSEMessageListener> = new Map()
|
||||||
private options: {
|
private statusListeners: Map<string, SSEStatusListener> = new Map()
|
||||||
backgroundCloseDelay: number
|
private options: SSEManagerOptions
|
||||||
reconnectDelay: number
|
|
||||||
maxReconnectAttempts: number
|
|
||||||
}
|
|
||||||
private reconnectAttempts = 0
|
private reconnectAttempts = 0
|
||||||
private isConnecting = false
|
private isConnecting = false
|
||||||
|
private isDestroyed = false
|
||||||
|
private connectionStatus: SSEConnectionStatus = 'idle'
|
||||||
private readonly handleVisibilityChange = () => {
|
private readonly handleVisibilityChange = () => {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
this.handleBackground()
|
this.handleBackground()
|
||||||
@@ -27,12 +39,14 @@ export class SSEManager {
|
|||||||
this.destroy()
|
this.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(url: string, options: Partial<typeof SSEManager.prototype.options> = {}) {
|
constructor(url: string, options: Partial<SSEManagerOptions> = {}) {
|
||||||
this.url = url
|
this.url = url
|
||||||
this.options = {
|
this.options = {
|
||||||
backgroundCloseDelay: 5000, // 5秒后关闭后台连接
|
backgroundCloseDelay: 5000, // 5秒后关闭后台连接
|
||||||
reconnectDelay: 3000, // 3秒后重连
|
reconnectDelay: 3000, // 3秒后重连
|
||||||
maxReconnectAttempts: 3,
|
maxReconnectAttempts: 3,
|
||||||
|
reconnectBackoffMultiplier: 1.5,
|
||||||
|
maxReconnectDelay: 30_000,
|
||||||
...options,
|
...options,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,38 +64,44 @@ export class SSEManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleBackground() {
|
private handleBackground() {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
this.isBackground = true
|
this.isBackground = true
|
||||||
|
this.clearReconnectTimer()
|
||||||
|
|
||||||
// 延迟关闭SSE连接,避免频繁切换
|
// 延迟关闭SSE连接,避免频繁切换
|
||||||
if (this.backgroundCloseTimer) {
|
this.clearBackgroundCloseTimer()
|
||||||
clearTimeout(this.backgroundCloseTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.backgroundCloseTimer = window.setTimeout(() => {
|
this.backgroundCloseTimer = window.setTimeout(() => {
|
||||||
if (this.isBackground && this.eventSource) {
|
if (this.isBackground && this.eventSource) {
|
||||||
this.eventSource.close()
|
this.closeCurrentEventSource()
|
||||||
this.eventSource = null
|
this.setConnectionStatus('closed')
|
||||||
}
|
}
|
||||||
}, this.options.backgroundCloseDelay)
|
}, this.options.backgroundCloseDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleForeground() {
|
private handleForeground() {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
this.isBackground = false
|
this.isBackground = false
|
||||||
|
|
||||||
// 清除后台关闭定时器
|
// 清除后台关闭定时器
|
||||||
if (this.backgroundCloseTimer) {
|
this.clearBackgroundCloseTimer()
|
||||||
clearTimeout(this.backgroundCloseTimer)
|
|
||||||
this.backgroundCloseTimer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只有在有活跃监听器时才重新建立连接
|
// 只有在有活跃监听器时才重新建立连接
|
||||||
if (this.listeners.size > 0 && (!this.eventSource || this.eventSource.readyState === EventSource.CLOSED)) {
|
if (this.listeners.size > 0 && (!this.eventSource || this.eventSource.readyState === EventSource.CLOSED)) {
|
||||||
this.reconnectSSE()
|
this.reconnectSSE(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private reconnectSSE(attemptCount = 0) {
|
private reconnectSSE(attemptCount = 0) {
|
||||||
if (attemptCount >= this.options.maxReconnectAttempts) {
|
if (this.isDestroyed || this.isBackground || this.listeners.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptCount > this.options.maxReconnectAttempts) {
|
||||||
|
this.reconnectAttempts = this.options.maxReconnectAttempts
|
||||||
|
this.setConnectionStatus('closed')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,40 +109,38 @@ export class SSEManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有活跃的监听器,不进行重连
|
this.clearReconnectTimer()
|
||||||
if (this.listeners.size === 0) {
|
this.closeCurrentEventSource()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isConnecting = true
|
this.isConnecting = true
|
||||||
this.reconnectAttempts = attemptCount
|
this.reconnectAttempts = attemptCount
|
||||||
|
this.setConnectionStatus('connecting')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.eventSource = new EventSource(this.url)
|
const source = new EventSource(this.url)
|
||||||
|
this.eventSource = source
|
||||||
|
|
||||||
this.eventSource.onopen = () => {
|
source.onopen = () => {
|
||||||
|
if (source !== this.eventSource) return
|
||||||
this.isConnecting = false
|
this.isConnecting = false
|
||||||
this.reconnectAttempts = 0
|
this.reconnectAttempts = 0
|
||||||
|
this.setConnectionStatus('open')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSource.onerror = error => {
|
source.onerror = () => {
|
||||||
|
if (source !== this.eventSource) return
|
||||||
|
|
||||||
this.isConnecting = false
|
this.isConnecting = false
|
||||||
|
this.setConnectionStatus('error')
|
||||||
|
|
||||||
if (this.eventSource?.readyState === EventSource.CLOSED) {
|
if (source.readyState === EventSource.CLOSED) {
|
||||||
// 连接已关闭,尝试重连
|
this.closeCurrentEventSource()
|
||||||
if (this.reconnectTimer) {
|
this.scheduleReconnect(this.reconnectAttempts + 1)
|
||||||
clearTimeout(this.reconnectTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reconnectTimer = window.setTimeout(() => {
|
|
||||||
if (!this.isBackground && this.listeners.size > 0) {
|
|
||||||
this.reconnectSSE(this.reconnectAttempts + 1)
|
|
||||||
}
|
|
||||||
}, this.options.reconnectDelay)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSource.onmessage = event => {
|
source.onmessage = event => {
|
||||||
|
if (source !== this.eventSource || this.isDestroyed) return
|
||||||
|
|
||||||
// 分发消息给所有监听器
|
// 分发消息给所有监听器
|
||||||
this.listeners.forEach((listener, listenerId) => {
|
this.listeners.forEach((listener, listenerId) => {
|
||||||
try {
|
try {
|
||||||
@@ -135,29 +153,95 @@ export class SSEManager {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.isConnecting = false
|
this.isConnecting = false
|
||||||
|
this.setConnectionStatus('error')
|
||||||
|
|
||||||
// 连接创建失败,尝试重连
|
// 连接创建失败,尝试重连
|
||||||
if (this.reconnectTimer) {
|
this.scheduleReconnect(this.reconnectAttempts + 1)
|
||||||
clearTimeout(this.reconnectTimer)
|
console.error('SSE: 连接创建失败', error)
|
||||||
}
|
|
||||||
|
|
||||||
this.reconnectTimer = window.setTimeout(() => {
|
|
||||||
if (!this.isBackground && this.listeners.size > 0) {
|
|
||||||
this.reconnectSSE(this.reconnectAttempts + 1)
|
|
||||||
}
|
|
||||||
}, this.options.reconnectDelay)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scheduleReconnect(attemptCount: number) {
|
||||||
|
if (this.isDestroyed || this.isBackground || this.listeners.size === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptCount > this.options.maxReconnectAttempts) {
|
||||||
|
this.reconnectAttempts = this.options.maxReconnectAttempts
|
||||||
|
this.setConnectionStatus('closed')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearReconnectTimer()
|
||||||
|
this.reconnectAttempts = attemptCount
|
||||||
|
|
||||||
|
// 失败越多等待越久,避免网络波动时短时间内打满连接。
|
||||||
|
const reconnectDelay = Math.min(
|
||||||
|
this.options.reconnectDelay * this.options.reconnectBackoffMultiplier ** Math.max(0, attemptCount - 1),
|
||||||
|
this.options.maxReconnectDelay,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.reconnectTimer = window.setTimeout(() => {
|
||||||
|
this.reconnectTimer = null
|
||||||
|
this.reconnectSSE(attemptCount)
|
||||||
|
}, reconnectDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeCurrentEventSource() {
|
||||||
|
if (!this.eventSource) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventSource.onopen = null
|
||||||
|
this.eventSource.onerror = null
|
||||||
|
this.eventSource.onmessage = null
|
||||||
|
this.eventSource.close()
|
||||||
|
this.eventSource = null
|
||||||
|
this.isConnecting = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearReconnectTimer() {
|
||||||
|
if (!this.reconnectTimer) return
|
||||||
|
|
||||||
|
clearTimeout(this.reconnectTimer)
|
||||||
|
this.reconnectTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearBackgroundCloseTimer() {
|
||||||
|
if (!this.backgroundCloseTimer) return
|
||||||
|
|
||||||
|
clearTimeout(this.backgroundCloseTimer)
|
||||||
|
this.backgroundCloseTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private setConnectionStatus(status: SSEConnectionStatus) {
|
||||||
|
if (this.connectionStatus === status) return
|
||||||
|
|
||||||
|
this.connectionStatus = status
|
||||||
|
this.statusListeners.forEach((listener, listenerId) => {
|
||||||
|
try {
|
||||||
|
listener(status)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`SSE: 状态监听器错误 [${listenerId}]`, error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加消息监听器
|
* 添加消息监听器
|
||||||
*/
|
*/
|
||||||
addMessageListener(id: string, listener: (event: MessageEvent) => void) {
|
addMessageListener(id: string, listener: SSEMessageListener) {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
this.listeners.set(id, listener)
|
this.listeners.set(id, listener)
|
||||||
|
|
||||||
// 如果还没有连接且不在后台,现在建立连接
|
// 如果还没有连接且不在后台,现在建立连接
|
||||||
if (!this.eventSource && !this.isBackground && !this.isConnecting) {
|
if (
|
||||||
this.reconnectSSE()
|
!this.isBackground &&
|
||||||
|
!this.isConnecting &&
|
||||||
|
(!this.eventSource || this.eventSource.readyState === EventSource.CLOSED)
|
||||||
|
) {
|
||||||
|
this.reconnectSSE(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +249,8 @@ export class SSEManager {
|
|||||||
* 移除消息监听器
|
* 移除消息监听器
|
||||||
*/
|
*/
|
||||||
removeMessageListener(id: string) {
|
removeMessageListener(id: string) {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
this.listeners.delete(id)
|
this.listeners.delete(id)
|
||||||
|
|
||||||
// 如果没有监听器了,关闭连接
|
// 如果没有监听器了,关闭连接
|
||||||
@@ -184,25 +270,17 @@ export class SSEManager {
|
|||||||
* 销毁管理器并清理所有引用
|
* 销毁管理器并清理所有引用
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
|
this.isDestroyed = true
|
||||||
this.resetConnectionState(true)
|
this.resetConnectionState(true)
|
||||||
this.removeVisibilityListener()
|
this.removeVisibilityListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetConnectionState(clearListeners = false) {
|
private resetConnectionState(clearListeners = false) {
|
||||||
if (this.eventSource) {
|
this.closeCurrentEventSource()
|
||||||
this.eventSource.close()
|
this.clearReconnectTimer()
|
||||||
this.eventSource = null
|
this.clearBackgroundCloseTimer()
|
||||||
}
|
|
||||||
|
|
||||||
if (this.reconnectTimer) {
|
|
||||||
clearTimeout(this.reconnectTimer)
|
|
||||||
this.reconnectTimer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.backgroundCloseTimer) {
|
|
||||||
clearTimeout(this.backgroundCloseTimer)
|
|
||||||
this.backgroundCloseTimer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clearListeners) {
|
if (clearListeners) {
|
||||||
this.listeners.clear()
|
this.listeners.clear()
|
||||||
@@ -210,6 +288,31 @@ export class SSEManager {
|
|||||||
|
|
||||||
this.isConnecting = false
|
this.isConnecting = false
|
||||||
this.reconnectAttempts = 0
|
this.reconnectAttempts = 0
|
||||||
|
this.setConnectionStatus(this.listeners.size > 0 ? 'closed' : 'idle')
|
||||||
|
|
||||||
|
if (clearListeners) {
|
||||||
|
this.statusListeners.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加连接状态监听器
|
||||||
|
*/
|
||||||
|
addStatusListener(id: string, listener: SSEStatusListener, emitCurrent = true) {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
|
this.statusListeners.set(id, listener)
|
||||||
|
|
||||||
|
if (emitCurrent) {
|
||||||
|
listener(this.connectionStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除连接状态监听器
|
||||||
|
*/
|
||||||
|
removeStatusListener(id: string) {
|
||||||
|
this.statusListeners.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,6 +322,13 @@ export class SSEManager {
|
|||||||
return this.eventSource?.readyState ?? EventSource.CLOSED
|
return this.eventSource?.readyState ?? EventSource.CLOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内部连接状态
|
||||||
|
*/
|
||||||
|
get status(): SSEConnectionStatus {
|
||||||
|
return this.connectionStatus
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取连接URL
|
* 获取连接URL
|
||||||
*/
|
*/
|
||||||
@@ -230,10 +340,12 @@ export class SSEManager {
|
|||||||
* 强制重新连接
|
* 强制重新连接
|
||||||
*/
|
*/
|
||||||
forceReconnect() {
|
forceReconnect() {
|
||||||
|
if (this.isDestroyed) return
|
||||||
|
|
||||||
const hasActiveListeners = this.listeners.size > 0
|
const hasActiveListeners = this.listeners.size > 0
|
||||||
this.close()
|
this.close()
|
||||||
if (!this.isBackground && hasActiveListeners) {
|
if (!this.isBackground && hasActiveListeners) {
|
||||||
this.reconnectSSE()
|
this.reconnectSSE(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useTheme } from 'vuetify'
|
|||||||
import { hexToRgb } from '@layouts/utils'
|
import { hexToRgb } from '@layouts/utils'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -122,7 +122,7 @@ async function loadCpuData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
const { loading } = useDataRefresh(
|
const { loading } = useDataRefresh(
|
||||||
'analytics-cpu',
|
'analytics-cpu',
|
||||||
loadCpuData,
|
loadCpuData,
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { hexToRgb } from '@layouts/utils'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { formatBytes } from '@/@core/utils/formatters'
|
import { formatBytes } from '@/@core/utils/formatters'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -127,7 +127,7 @@ async function loadMemoryData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
const { loading } = useDataRefresh(
|
const { loading } = useDataRefresh(
|
||||||
'analytics-memory',
|
'analytics-memory',
|
||||||
loadMemoryData,
|
loadMemoryData,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useTheme } from 'vuetify'
|
|||||||
import { hexToRgb } from '@layouts/utils'
|
import { hexToRgb } from '@layouts/utils'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -160,7 +160,7 @@ async function getNetworkUsage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
useDataRefresh(
|
useDataRefresh(
|
||||||
'dashboard-network',
|
'dashboard-network',
|
||||||
getNetworkUsage,
|
getNetworkUsage,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { formatSeconds } from '@/@core/utils/formatters'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Process } from '@/api/types'
|
import type { Process } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 表头
|
// 表头
|
||||||
const headers = [
|
const headers = [
|
||||||
@@ -31,7 +31,7 @@ async function loadProcessList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
useDataRefresh(
|
useDataRefresh(
|
||||||
'dashboard-processes',
|
'dashboard-processes',
|
||||||
loadProcessList,
|
loadProcessList,
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { ScheduleInfo } from '@/api/types'
|
import type { ScheduleInfo } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -34,7 +34,7 @@ async function loadSchedulerList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
useDataRefresh(
|
useDataRefresh(
|
||||||
'dashboard-scheduler',
|
'dashboard-scheduler',
|
||||||
loadSchedulerList,
|
loadSchedulerList,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { formatFileSize } from '@/@core/utils/formatters'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { DownloaderInfo } from '@/api/types'
|
import type { DownloaderInfo } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -77,7 +77,7 @@ async function loadDownloaderInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
const { loading } = useDataRefresh(
|
const { loading } = useDataRefresh(
|
||||||
'analytics-speed',
|
'analytics-speed',
|
||||||
loadDownloaderInfo,
|
loadDownloaderInfo,
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import DownloadingCard from '@/components/cards/DownloadingCard.vue'
|
|||||||
import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
|
import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
|
||||||
import { useUserStore } from '@/stores'
|
import { useUserStore } from '@/stores'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 定义输入参数
|
// 定义输入参数
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -56,7 +56,7 @@ const filteredDataList = computed(() => {
|
|||||||
else return dataList.value.filter(data => data.userid === userName || data.username === userName)
|
else return dataList.value.filter(data => data.userid === userName || data.username === userName)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
const { loading: dataLoading } = useDataRefresh(
|
const { loading: dataLoading } = useDataRefresh(
|
||||||
'downloading-list',
|
'downloading-list',
|
||||||
fetchData,
|
fetchData,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import { usePWA } from '@/composables/usePWA'
|
import { usePWA } from '@/composables/usePWA'
|
||||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||||
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
import { useGlobalSettingsStore } from '@/stores'
|
import { useGlobalSettingsStore } from '@/stores'
|
||||||
|
|
||||||
// i18n
|
// i18n
|
||||||
@@ -27,7 +27,7 @@ const globalSettingsStore = useGlobalSettingsStore()
|
|||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
// PWA模式检测
|
// PWA模式检测
|
||||||
const { appMode } = usePWA()
|
const { appMode } = usePWA()
|
||||||
const { useProgressSSE } = useBackgroundOptimization()
|
const { useProgressSSE } = useBackground()
|
||||||
|
|
||||||
// 计算列表可用高度
|
// 计算列表可用高度
|
||||||
// componentOffset = VCardItem搜索栏(68) + VDivider(1) + 分页栏(40) + VCard边距(2) = 111
|
// componentOffset = VCardItem搜索栏(68) + VDivider(1) + 分页栏(40) + VCard边距(2) = 111
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useTheme } from 'vuetify'
|
import { useTheme } from 'vuetify'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
import { useAvailableHeight } from '@/composables/useAvailableHeight'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ const props = defineProps<{
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
const { useSSE } = useBackgroundOptimization()
|
const { useSSE } = useBackground()
|
||||||
|
|
||||||
const DEFAULT_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
const DEFAULT_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
||||||
const MAX_LOG_LINES = 600
|
const MAX_LOG_LINES = 600
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { Message } from '@/api/types'
|
|||||||
import MessageCard from '@/components/cards/MessageCard.vue'
|
import MessageCard from '@/components/cards/MessageCard.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useSSE } = useBackgroundOptimization()
|
const { useSSE } = useBackground()
|
||||||
|
|
||||||
// 消息列表
|
// 消息列表
|
||||||
const messages = ref<Message[]>([])
|
const messages = ref<Message[]>([])
|
||||||
@@ -201,7 +201,7 @@ function handleSSEMessage(event: MessageEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的SSE连接
|
// 使用SSE连接
|
||||||
const { manager, isConnected } = useSSE(
|
const { manager, isConnected } = useSSE(
|
||||||
`${import.meta.env.VITE_API_BASE_URL}system/message?role=user`,
|
`${import.meta.env.VITE_API_BASE_URL}system/message?role=user`,
|
||||||
handleSSEMessage,
|
handleSSEMessage,
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useToast } from 'vue-toastification'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { ScheduleInfo } from '@/api/types'
|
import type { ScheduleInfo } from '@/api/types'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
|
import { useBackground } from '@/composables/useBackground'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { useDataRefresh } = useBackgroundOptimization()
|
const { useDataRefresh } = useBackground()
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
@@ -59,7 +59,7 @@ function runCommand(id: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用优化的数据刷新定时器
|
// 使用数据刷新定时器
|
||||||
useDataRefresh(
|
useDataRefresh(
|
||||||
'scheduler-list',
|
'scheduler-list',
|
||||||
loadSchedulerList,
|
loadSchedulerList,
|
||||||
|
|||||||
Reference in New Issue
Block a user