mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-15 20:51:29 +08:00
feat: implement launch loading state management and footer navigation visibility control
This commit is contained in:
@@ -121,6 +121,12 @@
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
html[data-launch-loading="true"] .footer-nav-container {
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
#loading-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
31
src/App.vue
31
src/App.vue
@@ -12,6 +12,8 @@ import { addBackgroundTimer, removeBackgroundTimer } from '@/utils/backgroundMan
|
||||
import PWAInstallPrompt from '@/components/PWAInstallPrompt.vue'
|
||||
import SharedDialogHost from '@/components/dialog/SharedDialogHost.vue'
|
||||
import { applyStoredThemeCustomizerAppearance } from '@/composables/useThemeCustomizer'
|
||||
import { completeLaunchLoading } from '@/composables/useLaunchLoading'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
import { themeManager } from '@/utils/themeManager'
|
||||
import { applyDocumentThemeChrome, resolveThemeName } from '@/utils/themePalette'
|
||||
import { configureApexChartsTheme } from '@/utils/apexCharts'
|
||||
@@ -45,6 +47,7 @@ setI18nLanguage(localeValue as SupportedLocale)
|
||||
const authStore = useAuthStore()
|
||||
const isLogin = computed(() => authStore.token)
|
||||
const route = useRoute()
|
||||
const { initializePWA } = usePWA()
|
||||
|
||||
// 全局设置store
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
@@ -245,19 +248,25 @@ function scheduleAuthenticatedStateInitialization() {
|
||||
}
|
||||
|
||||
// 添加logo动画效果并延迟移除加载界面
|
||||
function animateAndRemoveLoader() {
|
||||
async function animateAndRemoveLoader() {
|
||||
const loadingBg = document.querySelector('#loading-bg') as HTMLElement
|
||||
if (loadingBg) {
|
||||
// 只收掉启动内容,背景层保持实色直到节点被移除,避免底部 safe area 先透出页面内容。
|
||||
loadingBg.classList.add('loading-complete')
|
||||
window.setTimeout(() => {
|
||||
removeEl('#loading-bg')
|
||||
await new Promise<void>(resolve => {
|
||||
window.setTimeout(() => {
|
||||
removeEl('#loading-bg')
|
||||
|
||||
// 启动阶段的根节点锁定只在 loader 存在时生效,移除后恢复正常页面与弹窗布局。
|
||||
document.documentElement.removeAttribute('data-launch-loading')
|
||||
document.documentElement.style.removeProperty('overflow')
|
||||
document.body.style.removeProperty('overflow')
|
||||
}, 120)
|
||||
// 启动阶段的根节点锁定只在 loader 存在时生效,移除后恢复正常页面与弹窗布局。
|
||||
document.documentElement.removeAttribute('data-launch-loading')
|
||||
document.documentElement.style.removeProperty('overflow')
|
||||
document.body.style.removeProperty('overflow')
|
||||
completeLaunchLoading()
|
||||
resolve()
|
||||
}, 120)
|
||||
})
|
||||
} else {
|
||||
completeLaunchLoading()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,13 +283,15 @@ async function removeLoadingWithStateCheck() {
|
||||
}
|
||||
globalLoadingStateManager.setLoadingState('pwa-state', false)
|
||||
|
||||
// PWA/App 模式会影响布局和底部导航,必须在启动屏退场前稳定下来。
|
||||
await initializePWA()
|
||||
await initializeAuthenticatedState()
|
||||
|
||||
// 等待所有加载完成
|
||||
await globalLoadingStateManager.waitForAllComplete()
|
||||
|
||||
// 移除加载界面
|
||||
animateAndRemoveLoader()
|
||||
await animateAndRemoveLoader()
|
||||
|
||||
// 检查未读消息
|
||||
if (isLogin.value) {
|
||||
@@ -289,7 +300,7 @@ async function removeLoadingWithStateCheck() {
|
||||
} catch (error) {
|
||||
// 即使出错也要移除加载界面
|
||||
globalLoadingStateManager.reset()
|
||||
animateAndRemoveLoader()
|
||||
await animateAndRemoveLoader()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
src/composables/useLaunchLoading.ts
Normal file
20
src/composables/useLaunchLoading.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { readonly, ref } from 'vue'
|
||||
|
||||
function detectInitialLaunchLoading() {
|
||||
if (typeof document === 'undefined') return true
|
||||
|
||||
return document.documentElement.getAttribute('data-launch-loading') === 'true' || Boolean(document.getElementById('loading-bg'))
|
||||
}
|
||||
|
||||
// 启动屏的全局状态,供 Teleport 到 body 的组件避开 iOS PWA 启动阶段的固定层闪烁。
|
||||
const isLaunchLoading = ref(detectInitialLaunchLoading())
|
||||
|
||||
export function completeLaunchLoading() {
|
||||
isLaunchLoading.value = false
|
||||
}
|
||||
|
||||
export function useLaunchLoading() {
|
||||
return {
|
||||
isLaunchLoading: readonly(isLaunchLoading),
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@ import { NavMenu } from '@/@layouts/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { buildUserPermissionContext, filterItemsByPermission, filterMenusByPermission, hasItemPermission } from '@/utils/permission'
|
||||
import { useLaunchLoading } from '@/composables/useLaunchLoading'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
import type { DynamicButtonMenuItem } from '@/composables/useDynamicButton'
|
||||
|
||||
// 是否显示的输入参数
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
showNav: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@@ -19,6 +20,7 @@ defineProps({
|
||||
const display = useDisplay()
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
const { isLaunchLoading } = useLaunchLoading()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 判断当前是否为英文环境
|
||||
@@ -175,6 +177,8 @@ const visibleDynamicButtonMenuItems = computed(() => {
|
||||
})
|
||||
|
||||
const hasDynamicButtonMenu = computed(() => visibleDynamicButtonMenuItems.value.length > 0)
|
||||
const shouldRenderFooterNav = computed(() => appMode.value && props.showNav)
|
||||
const shouldRevealFooterNav = computed(() => shouldRenderFooterNav.value && !isLaunchLoading.value)
|
||||
|
||||
const legacyDynamicMenuTitleKeyMap: Record<string, string> = {
|
||||
'components.subscribeHistory.title': 'dialog.subscribeHistory.title',
|
||||
@@ -212,8 +216,8 @@ function handleDynamicMenuItemClick(item: DynamicButtonMenuItem) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport v-if="appMode && showNav" to="body">
|
||||
<div class="footer-nav-container">
|
||||
<Teleport v-if="shouldRenderFooterNav" to="body">
|
||||
<div v-show="shouldRevealFooterNav" class="footer-nav-container">
|
||||
<TransitionGroup name="footer-nav" tag="div" class="footer-nav-group">
|
||||
<VCard key="main-nav" elevation="3" class="footer-nav-card border" rounded="pill">
|
||||
<VCardText class="footer-card-content">
|
||||
|
||||
Reference in New Issue
Block a user