mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
Merge branch 'v2' into cursor/analyze-factors-causing-ios-to-kill-pwa-ac82
This commit is contained in:
@@ -43,3 +43,29 @@ export const isPWA = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
return (window.navigator as any).standalone === true
|
return (window.navigator as any).standalone === true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步检测PWA显示模式
|
||||||
|
export const isPWADisplayMode = (): boolean => {
|
||||||
|
return (
|
||||||
|
window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
(window.navigator as any).standalone ||
|
||||||
|
document.referrer.includes('android-app://')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全面的PWA检测(推荐使用)
|
||||||
|
export const checkPWAStatus = async () => {
|
||||||
|
const hasServiceWorker = await isPWA()
|
||||||
|
const isStandaloneMode = isPWADisplayMode()
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 是否有PWA功能(Service Worker)
|
||||||
|
hasPWAFeatures: hasServiceWorker,
|
||||||
|
// 是否在独立显示模式下运行
|
||||||
|
isStandaloneMode,
|
||||||
|
// 综合判断:至少满足一个条件就认为是PWA环境
|
||||||
|
isPWAEnvironment: hasServiceWorker || isStandaloneMode,
|
||||||
|
// 完整的PWA体验:既有功能又在独立模式下运行
|
||||||
|
isFullPWA: hasServiceWorker && isStandaloneMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+166
-11
@@ -1,9 +1,15 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { isPWA } from '@/@core/utils/navigator'
|
import { checkPWAStatus, isPWADisplayMode } from '@/@core/utils/navigator'
|
||||||
|
import type { PWAState } from '@/utils/pwaStateManager'
|
||||||
|
|
||||||
// 全局PWA状态,确保只初始化一次
|
// 全局PWA状态,确保只初始化一次
|
||||||
const globalPwaMode = ref<boolean | null>(null)
|
const globalPwaStatus = ref<{
|
||||||
|
hasPWAFeatures: boolean
|
||||||
|
isStandaloneMode: boolean
|
||||||
|
isPWAEnvironment: boolean
|
||||||
|
isFullPWA: boolean
|
||||||
|
} | null>(null)
|
||||||
const globalLoading = ref(false)
|
const globalLoading = ref(false)
|
||||||
let initPromise: Promise<void> | null = null
|
let initPromise: Promise<void> | null = null
|
||||||
|
|
||||||
@@ -11,16 +17,21 @@ let initPromise: Promise<void> | null = null
|
|||||||
async function initializePWAGlobally() {
|
async function initializePWAGlobally() {
|
||||||
if (initPromise) return initPromise
|
if (initPromise) return initPromise
|
||||||
|
|
||||||
if (globalPwaMode.value !== null || globalLoading.value) return Promise.resolve()
|
if (globalPwaStatus.value !== null || globalLoading.value) return Promise.resolve()
|
||||||
|
|
||||||
initPromise = new Promise(async (resolve, reject) => {
|
initPromise = new Promise(async (resolve, reject) => {
|
||||||
globalLoading.value = true
|
globalLoading.value = true
|
||||||
try {
|
try {
|
||||||
globalPwaMode.value = await isPWA()
|
globalPwaStatus.value = await checkPWAStatus()
|
||||||
resolve()
|
resolve()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to detect PWA mode', error)
|
console.error('Failed to detect PWA status', error)
|
||||||
globalPwaMode.value = false
|
globalPwaStatus.value = {
|
||||||
|
hasPWAFeatures: false,
|
||||||
|
isStandaloneMode: isPWADisplayMode(),
|
||||||
|
isPWAEnvironment: isPWADisplayMode(),
|
||||||
|
isFullPWA: false,
|
||||||
|
}
|
||||||
reject(error)
|
reject(error)
|
||||||
} finally {
|
} finally {
|
||||||
globalLoading.value = false
|
globalLoading.value = false
|
||||||
@@ -33,25 +44,169 @@ async function initializePWAGlobally() {
|
|||||||
export function usePWA() {
|
export function usePWA() {
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|
||||||
const appMode = computed(() => {
|
// 基于新的PWA状态结构
|
||||||
return globalPwaMode.value && display.mdAndDown.value
|
const pwaMode = computed(() => {
|
||||||
|
return globalPwaStatus.value?.isPWAEnvironment ?? false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const appMode = computed(() => {
|
||||||
|
return pwaMode.value && display.mdAndDown.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 详细的PWA状态信息
|
||||||
|
const pwaStatus = computed(() => globalPwaStatus.value)
|
||||||
|
|
||||||
// 自动初始化PWA检测
|
// 自动初始化PWA检测
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initializePWAGlobally().catch(console.error)
|
initializePWAGlobally().catch(console.error)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果是在服务端或首次调用,立即开始初始化
|
// 如果是在服务端或首次调用,立即开始初始化
|
||||||
if (typeof window !== 'undefined' && globalPwaMode.value === null && !globalLoading.value) {
|
if (typeof window !== 'undefined' && globalPwaStatus.value === null && !globalLoading.value) {
|
||||||
initializePWAGlobally().catch(console.error)
|
initializePWAGlobally().catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pwaMode: globalPwaMode,
|
pwaMode,
|
||||||
appMode,
|
appMode,
|
||||||
|
pwaStatus,
|
||||||
loading: globalLoading,
|
loading: globalLoading,
|
||||||
// 保留手动初始化方法以防需要
|
// 保留手动初始化方法以防需要
|
||||||
initializePWA: initializePWAGlobally,
|
initializePWA: initializePWAGlobally,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PWA状态管理 composable
|
||||||
|
export function usePWAState() {
|
||||||
|
const isStateRestored = ref(false)
|
||||||
|
const stateRestoreCount = ref(0)
|
||||||
|
const lastRestoredState = ref<PWAState | null>(null)
|
||||||
|
const isPWAMode = ref(false)
|
||||||
|
|
||||||
|
// 检查PWA模式 - 使用统一的检测方式
|
||||||
|
const checkPWAMode = async () => {
|
||||||
|
// 确保全局PWA状态已初始化
|
||||||
|
if (globalPwaStatus.value === null) {
|
||||||
|
await initializePWAGlobally()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取PWA状态
|
||||||
|
const status = globalPwaStatus.value
|
||||||
|
if (status) {
|
||||||
|
isPWAMode.value = status.isPWAEnvironment
|
||||||
|
} else {
|
||||||
|
// 如果状态获取失败,使用同步检测作为后备
|
||||||
|
isPWAMode.value = isPWADisplayMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存当前状态
|
||||||
|
const saveCurrentState = async () => {
|
||||||
|
if (window.pwaStateController) {
|
||||||
|
await window.pwaStateController.saveCurrentState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动触发状态恢复检查
|
||||||
|
const checkStateRestore = async () => {
|
||||||
|
if (window.pwaStateController) {
|
||||||
|
// 静默检查
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听状态恢复事件
|
||||||
|
const handleStateRestored = (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent<{ state: PWAState }>
|
||||||
|
isStateRestored.value = true
|
||||||
|
stateRestoreCount.value++
|
||||||
|
lastRestoredState.value = customEvent.detail.state
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态恢复标志
|
||||||
|
const resetStateRestored = () => {
|
||||||
|
isStateRestored.value = false
|
||||||
|
lastRestoredState.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查状态管理器是否可用
|
||||||
|
const isStateManagerAvailable = () => {
|
||||||
|
return !!window.pwaStateController
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkPWAMode()
|
||||||
|
|
||||||
|
// 监听状态恢复事件
|
||||||
|
window.addEventListener('pwaStateRestored', handleStateRestored)
|
||||||
|
|
||||||
|
// 监听PWA模式变化
|
||||||
|
const mediaQuery = window.matchMedia('(display-mode: standalone)')
|
||||||
|
const handleDisplayModeChange = (e: MediaQueryListEvent) => {
|
||||||
|
isPWAMode.value = e.matches
|
||||||
|
// 同步更新全局PWA状态
|
||||||
|
if (globalPwaStatus.value) {
|
||||||
|
globalPwaStatus.value.isStandaloneMode = e.matches
|
||||||
|
globalPwaStatus.value.isPWAEnvironment = globalPwaStatus.value.hasPWAFeatures || e.matches
|
||||||
|
globalPwaStatus.value.isFullPWA = globalPwaStatus.value.hasPWAFeatures && e.matches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaQuery.addEventListener) {
|
||||||
|
mediaQuery.addEventListener('change', handleDisplayModeChange)
|
||||||
|
} else {
|
||||||
|
mediaQuery.addListener(handleDisplayModeChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (mediaQuery.removeEventListener) {
|
||||||
|
mediaQuery.removeEventListener('change', handleDisplayModeChange)
|
||||||
|
} else {
|
||||||
|
mediaQuery.removeListener(handleDisplayModeChange)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('pwaStateRestored', handleStateRestored)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 响应式状态
|
||||||
|
isPWAMode,
|
||||||
|
isStateRestored,
|
||||||
|
stateRestoreCount,
|
||||||
|
lastRestoredState,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
saveCurrentState,
|
||||||
|
checkStateRestore,
|
||||||
|
resetStateRestored,
|
||||||
|
isStateManagerAvailable,
|
||||||
|
checkPWAMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局PWA状态管理器
|
||||||
|
export function useGlobalPWAState() {
|
||||||
|
// 检查是否在PWA环境中 - 使用统一的检测方式
|
||||||
|
const isPWAEnvironment = () => {
|
||||||
|
return globalPwaStatus.value?.isPWAEnvironment ?? isPWADisplayMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取存储的状态
|
||||||
|
const getStoredState = () => {
|
||||||
|
return localStorage.getItem('mp-pwa-app-state')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除存储的状态
|
||||||
|
const clearStoredState = () => {
|
||||||
|
localStorage.removeItem('mp-pwa-app-state')
|
||||||
|
sessionStorage.removeItem('mp-pwa-session-state')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isPWAEnvironment,
|
||||||
|
getStoredState,
|
||||||
|
clearStoredState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
|
||||||
import type { PWAState } from '@/utils/pwaStateManager'
|
|
||||||
|
|
||||||
export function usePWAState() {
|
|
||||||
const isStateRestored = ref(false)
|
|
||||||
const stateRestoreCount = ref(0)
|
|
||||||
const lastRestoredState = ref<PWAState | null>(null)
|
|
||||||
const isPWAMode = ref(false)
|
|
||||||
|
|
||||||
// 检查PWA模式
|
|
||||||
const checkPWAMode = () => {
|
|
||||||
isPWAMode.value = window.matchMedia('(display-mode: standalone)').matches ||
|
|
||||||
(window.navigator as any).standalone ||
|
|
||||||
document.referrer.includes('android-app://')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存当前状态
|
|
||||||
const saveCurrentState = async () => {
|
|
||||||
if (window.pwaStateController) {
|
|
||||||
await window.pwaStateController.saveCurrentState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动触发状态恢复检查
|
|
||||||
const checkStateRestore = async () => {
|
|
||||||
if (window.pwaStateController) {
|
|
||||||
// 静默检查
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听状态恢复事件
|
|
||||||
const handleStateRestored = (event: Event) => {
|
|
||||||
const customEvent = event as CustomEvent<{ state: PWAState }>
|
|
||||||
isStateRestored.value = true
|
|
||||||
stateRestoreCount.value++
|
|
||||||
lastRestoredState.value = customEvent.detail.state
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置状态恢复标志
|
|
||||||
const resetStateRestored = () => {
|
|
||||||
isStateRestored.value = false
|
|
||||||
lastRestoredState.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查状态管理器是否可用
|
|
||||||
const isStateManagerAvailable = () => {
|
|
||||||
return !!window.pwaStateController
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
checkPWAMode()
|
|
||||||
|
|
||||||
// 监听状态恢复事件
|
|
||||||
window.addEventListener('pwaStateRestored', handleStateRestored)
|
|
||||||
|
|
||||||
// 监听PWA模式变化
|
|
||||||
const mediaQuery = window.matchMedia('(display-mode: standalone)')
|
|
||||||
const handleDisplayModeChange = (e: MediaQueryListEvent) => {
|
|
||||||
isPWAMode.value = e.matches
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaQuery.addEventListener) {
|
|
||||||
mediaQuery.addEventListener('change', handleDisplayModeChange)
|
|
||||||
} else {
|
|
||||||
mediaQuery.addListener(handleDisplayModeChange)
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (mediaQuery.removeEventListener) {
|
|
||||||
mediaQuery.removeEventListener('change', handleDisplayModeChange)
|
|
||||||
} else {
|
|
||||||
mediaQuery.removeListener(handleDisplayModeChange)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('pwaStateRestored', handleStateRestored)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
// 响应式状态
|
|
||||||
isPWAMode,
|
|
||||||
isStateRestored,
|
|
||||||
stateRestoreCount,
|
|
||||||
lastRestoredState,
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
saveCurrentState,
|
|
||||||
checkStateRestore,
|
|
||||||
resetStateRestored,
|
|
||||||
isStateManagerAvailable,
|
|
||||||
checkPWAMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局PWA状态管理器
|
|
||||||
export function useGlobalPWAState() {
|
|
||||||
// 检查是否在PWA环境中
|
|
||||||
const isPWAEnvironment = () => {
|
|
||||||
return window.matchMedia('(display-mode: standalone)').matches ||
|
|
||||||
(window.navigator as any).standalone ||
|
|
||||||
document.referrer.includes('android-app://')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取存储的状态
|
|
||||||
const getStoredState = () => {
|
|
||||||
return localStorage.getItem('mp-pwa-app-state')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除存储的状态
|
|
||||||
const clearStoredState = () => {
|
|
||||||
localStorage.removeItem('mp-pwa-app-state')
|
|
||||||
sessionStorage.removeItem('mp-pwa-session-state')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isPWAEnvironment,
|
|
||||||
getStoredState,
|
|
||||||
clearStoredState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-12
@@ -47,26 +47,25 @@ import '@/styles/main.scss'
|
|||||||
import { PWAStateController } from '@/utils/pwaStateManager'
|
import { PWAStateController } from '@/utils/pwaStateManager'
|
||||||
import { backgroundManager } from '@/utils/backgroundManager'
|
import { backgroundManager } from '@/utils/backgroundManager'
|
||||||
import { sseManagerSingleton } from '@/utils/sseManager'
|
import { sseManagerSingleton } from '@/utils/sseManager'
|
||||||
|
import { checkPWAStatus } from '@/@core/utils/navigator'
|
||||||
|
|
||||||
// PWA状态管理器初始化函数
|
// PWA状态管理器初始化函数
|
||||||
const initializePWABeforeMount = async () => {
|
const initializePWABeforeMount = async () => {
|
||||||
// 检查是否在PWA模式下运行
|
// 使用统一的PWA检测方法
|
||||||
const isPWA = window.matchMedia('(display-mode: standalone)').matches ||
|
const pwaStatus = await checkPWAStatus()
|
||||||
(window.navigator as any).standalone ||
|
|
||||||
document.referrer.includes('android-app://')
|
if (pwaStatus.isPWAEnvironment) {
|
||||||
|
|
||||||
if (isPWA) {
|
|
||||||
const pwaStateController = new PWAStateController()
|
const pwaStateController = new PWAStateController()
|
||||||
|
|
||||||
// 等待状态恢复完成
|
// 等待状态恢复完成
|
||||||
await pwaStateController.waitForStateRestore()
|
await pwaStateController.waitForStateRestore()
|
||||||
|
|
||||||
// 将状态管理器绑定到全局对象
|
// 将状态管理器绑定到全局对象
|
||||||
;(window as any).pwaStateController = pwaStateController
|
;(window as any).pwaStateController = pwaStateController
|
||||||
|
|
||||||
return pwaStateController
|
return pwaStateController
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +124,12 @@ if (pwaStateController) {
|
|||||||
// 监听状态恢复事件
|
// 监听状态恢复事件
|
||||||
window.addEventListener('pwaStateRestored', (event: Event) => {
|
window.addEventListener('pwaStateRestored', (event: Event) => {
|
||||||
const customEvent = event as CustomEvent
|
const customEvent = event as CustomEvent
|
||||||
|
|
||||||
// 可以在这里添加状态恢复后的处理逻辑
|
// 可以在这里添加状态恢复后的处理逻辑
|
||||||
// 例如:通知Vue组件状态已恢复
|
// 例如:通知Vue组件状态已恢复
|
||||||
app.config.globalProperties.$pwaStateRestored = true
|
app.config.globalProperties.$pwaStateRestored = true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听应用即将卸载事件,保存状态
|
// 监听应用即将卸载事件,保存状态
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
if (pwaStateController) {
|
if (pwaStateController) {
|
||||||
|
|||||||
Reference in New Issue
Block a user