mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-29 22:56:45 +08:00
优化控制台与设置页切换时的 WebRTC 会话保活与恢复逻辑
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, watch } from 'vue'
|
import { KeepAlive, onMounted, watch } from 'vue'
|
||||||
import { RouterView, useRouter } from 'vue-router'
|
import { RouterView, useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useSystemStore } from '@/stores/system'
|
import { useSystemStore } from '@/stores/system'
|
||||||
@@ -56,5 +56,10 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RouterView />
|
<RouterView v-slot="{ Component, route }">
|
||||||
|
<KeepAlive v-if="authStore.isAuthenticated">
|
||||||
|
<component :is="Component" v-if="route.name === 'Console'" />
|
||||||
|
</KeepAlive>
|
||||||
|
<component :is="Component" v-if="route.name !== 'Console' || !authStore.isAuthenticated" />
|
||||||
|
</RouterView>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
|
import { ref, onMounted, onUnmounted, onActivated, onDeactivated, computed, watch, nextTick } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useSystemStore } from '@/stores/system'
|
import { useSystemStore } from '@/stores/system'
|
||||||
@@ -137,6 +137,8 @@ let accumulatedDelta = { x: 0, y: 0 } // For relative mode: accumulate deltas be
|
|||||||
|
|
||||||
// Cursor visibility (from localStorage, updated via storage event)
|
// Cursor visibility (from localStorage, updated via storage event)
|
||||||
const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false')
|
const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false')
|
||||||
|
let interactionListenersBound = false
|
||||||
|
const isConsoleActive = ref(false)
|
||||||
|
|
||||||
function syncMouseModeFromConfig() {
|
function syncMouseModeFromConfig() {
|
||||||
const mouseAbsolute = configStore.hid?.mouse_absolute
|
const mouseAbsolute = configStore.hid?.mouse_absolute
|
||||||
@@ -610,6 +612,7 @@ function waitForVideoFirstFrame(el: HTMLVideoElement, timeoutMs = 2000): Promise
|
|||||||
|
|
||||||
function shouldSuppressAutoReconnect(): boolean {
|
function shouldSuppressAutoReconnect(): boolean {
|
||||||
return videoMode.value === 'mjpeg'
|
return videoMode.value === 'mjpeg'
|
||||||
|
|| !isConsoleActive.value
|
||||||
|| videoSession.localSwitching.value
|
|| videoSession.localSwitching.value
|
||||||
|| videoSession.backendSwitching.value
|
|| videoSession.backendSwitching.value
|
||||||
|| videoRestarting.value
|
|| videoRestarting.value
|
||||||
@@ -1951,6 +1954,10 @@ function handlePointerLockError() {
|
|||||||
isPointerLocked.value = false
|
isPointerLocked.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFullscreenChange() {
|
||||||
|
isFullscreen.value = !!document.fullscreenElement
|
||||||
|
}
|
||||||
|
|
||||||
function handleBlur() {
|
function handleBlur() {
|
||||||
pressedKeys.value = []
|
pressedKeys.value = []
|
||||||
activeModifierMask.value = 0
|
activeModifierMask.value = 0
|
||||||
@@ -2003,6 +2010,71 @@ function handleMouseSendIntervalStorage(e: StorageEvent) {
|
|||||||
setMouseMoveSendInterval(loadMouseMoveSendIntervalFromStorage())
|
setMouseMoveSendInterval(loadMouseMoveSendIntervalFromStorage())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function registerInteractionListeners() {
|
||||||
|
if (interactionListenersBound) return
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
|
window.addEventListener('keyup', handleKeyUp)
|
||||||
|
window.addEventListener('blur', handleBlur)
|
||||||
|
window.addEventListener('mouseup', handleWindowMouseUp)
|
||||||
|
window.addEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
|
||||||
|
window.addEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
|
||||||
|
window.addEventListener('storage', handleMouseSendIntervalStorage)
|
||||||
|
|
||||||
|
document.addEventListener('pointerlockchange', handlePointerLockChange)
|
||||||
|
document.addEventListener('pointerlockerror', handlePointerLockError)
|
||||||
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||||
|
|
||||||
|
interactionListenersBound = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterInteractionListeners() {
|
||||||
|
if (!interactionListenersBound) return
|
||||||
|
|
||||||
|
window.removeEventListener('keydown', handleKeyDown)
|
||||||
|
window.removeEventListener('keyup', handleKeyUp)
|
||||||
|
window.removeEventListener('blur', handleBlur)
|
||||||
|
window.removeEventListener('mouseup', handleWindowMouseUp)
|
||||||
|
window.removeEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
|
||||||
|
window.removeEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
|
||||||
|
window.removeEventListener('storage', handleMouseSendIntervalStorage)
|
||||||
|
|
||||||
|
document.removeEventListener('pointerlockchange', handlePointerLockChange)
|
||||||
|
document.removeEventListener('pointerlockerror', handlePointerLockError)
|
||||||
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||||
|
|
||||||
|
interactionListenersBound = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function activateConsoleView() {
|
||||||
|
isConsoleActive.value = true
|
||||||
|
registerInteractionListeners()
|
||||||
|
|
||||||
|
if (videoMode.value !== 'mjpeg' && webrtc.videoTrack.value) {
|
||||||
|
await nextTick()
|
||||||
|
await rebindWebRTCVideo()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
videoMode.value !== 'mjpeg'
|
||||||
|
&& !webrtc.isConnected.value
|
||||||
|
&& !webrtc.isConnecting.value
|
||||||
|
&& !videoSession.localSwitching.value
|
||||||
|
&& !videoSession.backendSwitching.value
|
||||||
|
&& !initialModeRestoreInProgress
|
||||||
|
) {
|
||||||
|
await connectWebRTCOnly(videoMode.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivateConsoleView() {
|
||||||
|
isConsoleActive.value = false
|
||||||
|
handleBlur()
|
||||||
|
exitPointerLock()
|
||||||
|
unregisterInteractionListeners()
|
||||||
|
}
|
||||||
|
|
||||||
// ActionBar handlers
|
// ActionBar handlers
|
||||||
// (MSD and Settings are now handled by ActionBar component directly)
|
// (MSD and Settings are now handled by ActionBar component directly)
|
||||||
|
|
||||||
@@ -2074,30 +2146,12 @@ onMounted(async () => {
|
|||||||
syncMouseModeFromConfig()
|
syncMouseModeFromConfig()
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown)
|
|
||||||
window.addEventListener('keyup', handleKeyUp)
|
|
||||||
window.addEventListener('blur', handleBlur)
|
|
||||||
window.addEventListener('mouseup', handleWindowMouseUp)
|
|
||||||
|
|
||||||
setMouseMoveSendInterval(loadMouseMoveSendIntervalFromStorage())
|
setMouseMoveSendInterval(loadMouseMoveSendIntervalFromStorage())
|
||||||
|
|
||||||
// Listen for cursor visibility changes from HidConfigPopover
|
|
||||||
window.addEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
|
|
||||||
window.addEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
|
|
||||||
window.addEventListener('storage', handleMouseSendIntervalStorage)
|
|
||||||
|
|
||||||
watch(() => configStore.hid?.mouse_absolute, () => {
|
watch(() => configStore.hid?.mouse_absolute, () => {
|
||||||
syncMouseModeFromConfig()
|
syncMouseModeFromConfig()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Pointer Lock event listeners
|
|
||||||
document.addEventListener('pointerlockchange', handlePointerLockChange)
|
|
||||||
document.addEventListener('pointerlockerror', handlePointerLockError)
|
|
||||||
|
|
||||||
document.addEventListener('fullscreenchange', () => {
|
|
||||||
isFullscreen.value = !!document.fullscreenElement
|
|
||||||
})
|
|
||||||
|
|
||||||
const storedTheme = localStorage.getItem('theme')
|
const storedTheme = localStorage.getItem('theme')
|
||||||
if (storedTheme === 'dark' || (!storedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
if (storedTheme === 'dark' || (!storedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
isDark.value = true
|
isDark.value = true
|
||||||
@@ -2118,7 +2172,17 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
void activateConsoleView()
|
||||||
|
})
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
deactivateConsoleView()
|
||||||
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
deactivateConsoleView()
|
||||||
|
|
||||||
// Reset initial device info flag
|
// Reset initial device info flag
|
||||||
initialDeviceInfoReceived = false
|
initialDeviceInfoReceived = false
|
||||||
initialModeRestoreDone = false
|
initialModeRestoreDone = false
|
||||||
@@ -2154,18 +2218,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
// Exit pointer lock if active
|
// Exit pointer lock if active
|
||||||
exitPointerLock()
|
exitPointerLock()
|
||||||
|
|
||||||
window.removeEventListener('keydown', handleKeyDown)
|
|
||||||
window.removeEventListener('keyup', handleKeyUp)
|
|
||||||
window.removeEventListener('blur', handleBlur)
|
|
||||||
window.removeEventListener('mouseup', handleWindowMouseUp)
|
|
||||||
window.removeEventListener('hidCursorVisibilityChanged', handleCursorVisibilityChange as EventListener)
|
|
||||||
window.removeEventListener('hidMouseSendIntervalChanged', handleMouseSendIntervalChange as EventListener)
|
|
||||||
window.removeEventListener('storage', handleMouseSendIntervalStorage)
|
|
||||||
|
|
||||||
// Remove pointer lock event listeners
|
|
||||||
document.removeEventListener('pointerlockchange', handlePointerLockChange)
|
|
||||||
document.removeEventListener('pointerlockerror', handlePointerLockError)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { useSystemStore } from '@/stores/system'
|
import { useSystemStore } from '@/stores/system'
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
@@ -83,6 +84,7 @@ import {
|
|||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
|
|
||||||
const { t, te, locale } = useI18n()
|
const { t, te, locale } = useI18n()
|
||||||
|
const route = useRoute()
|
||||||
const systemStore = useSystemStore()
|
const systemStore = useSystemStore()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -92,6 +94,21 @@ const activeSection = ref('appearance')
|
|||||||
const mobileMenuOpen = ref(false)
|
const mobileMenuOpen = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saved = ref(false)
|
const saved = ref(false)
|
||||||
|
const SETTINGS_SECTION_IDS = new Set([
|
||||||
|
'appearance',
|
||||||
|
'account',
|
||||||
|
'access',
|
||||||
|
'video',
|
||||||
|
'hid',
|
||||||
|
'msd',
|
||||||
|
'atx',
|
||||||
|
'environment',
|
||||||
|
'ext-ttyd',
|
||||||
|
'ext-rustdesk',
|
||||||
|
'ext-rtsp',
|
||||||
|
'ext-remote-access',
|
||||||
|
'about',
|
||||||
|
])
|
||||||
|
|
||||||
// Navigation structure
|
// Navigation structure
|
||||||
const navGroups = computed(() => [
|
const navGroups = computed(() => [
|
||||||
@@ -135,6 +152,10 @@ function selectSection(id: string) {
|
|||||||
mobileMenuOpen.value = false
|
mobileMenuOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSettingsSection(value: unknown): string | null {
|
||||||
|
return typeof value === 'string' && SETTINGS_SECTION_IDS.has(value) ? value : null
|
||||||
|
}
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
const theme = ref<'light' | 'dark' | 'system'>('system')
|
const theme = ref<'light' | 'dark' | 'system'>('system')
|
||||||
|
|
||||||
@@ -1850,6 +1871,13 @@ watch(() => config.value.hid_backend, () => {
|
|||||||
otgSelfCheckResult.value = null
|
otgSelfCheckResult.value = null
|
||||||
otgSelfCheckError.value = ''
|
otgSelfCheckError.value = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => route.query.tab, (tab) => {
|
||||||
|
const section = normalizeSettingsSection(tab)
|
||||||
|
if (section && activeSection.value !== section) {
|
||||||
|
selectSection(section)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user