Merge branch 'v2' into cursor/analyze-factors-causing-ios-to-kill-pwa-ac82

This commit is contained in:
jxxghp
2025-07-06 23:41:42 +08:00
committed by GitHub
4 changed files with 203 additions and 145 deletions

View File

@@ -43,3 +43,29 @@ export const isPWA = async (): Promise<boolean> => {
}
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,
}
}

View File

@@ -1,9 +1,15 @@
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useDisplay } from 'vuetify'
import { isPWA } from '@/@core/utils/navigator'
import { checkPWAStatus, isPWADisplayMode } from '@/@core/utils/navigator'
import type { PWAState } from '@/utils/pwaStateManager'
// 全局PWA状态确保只初始化一次
const globalPwaMode = ref<boolean | null>(null)
const globalPwaStatus = ref<{
hasPWAFeatures: boolean
isStandaloneMode: boolean
isPWAEnvironment: boolean
isFullPWA: boolean
} | null>(null)
const globalLoading = ref(false)
let initPromise: Promise<void> | null = null
@@ -11,16 +17,21 @@ let initPromise: Promise<void> | null = null
async function initializePWAGlobally() {
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) => {
globalLoading.value = true
try {
globalPwaMode.value = await isPWA()
globalPwaStatus.value = await checkPWAStatus()
resolve()
} catch (error) {
console.error('Failed to detect PWA mode', error)
globalPwaMode.value = false
console.error('Failed to detect PWA status', error)
globalPwaStatus.value = {
hasPWAFeatures: false,
isStandaloneMode: isPWADisplayMode(),
isPWAEnvironment: isPWADisplayMode(),
isFullPWA: false,
}
reject(error)
} finally {
globalLoading.value = false
@@ -33,25 +44,169 @@ async function initializePWAGlobally() {
export function usePWA() {
const display = useDisplay()
const appMode = computed(() => {
return globalPwaMode.value && display.mdAndDown.value
// 基于新的PWA状态结构
const pwaMode = computed(() => {
return globalPwaStatus.value?.isPWAEnvironment ?? false
})
const appMode = computed(() => {
return pwaMode.value && display.mdAndDown.value
})
// 详细的PWA状态信息
const pwaStatus = computed(() => globalPwaStatus.value)
// 自动初始化PWA检测
onMounted(() => {
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)
}
return {
pwaMode: globalPwaMode,
pwaMode,
appMode,
pwaStatus,
loading: globalLoading,
// 保留手动初始化方法以防需要
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,
}
}

View File

@@ -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
}
}

View File

@@ -47,26 +47,25 @@ import '@/styles/main.scss'
import { PWAStateController } from '@/utils/pwaStateManager'
import { backgroundManager } from '@/utils/backgroundManager'
import { sseManagerSingleton } from '@/utils/sseManager'
import { checkPWAStatus } from '@/@core/utils/navigator'
// PWA状态管理器初始化函数
const initializePWABeforeMount = async () => {
// 检查是否在PWA模式下运行
const isPWA = window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone ||
document.referrer.includes('android-app://')
if (isPWA) {
// 使用统一的PWA检测方法
const pwaStatus = await checkPWAStatus()
if (pwaStatus.isPWAEnvironment) {
const pwaStateController = new PWAStateController()
// 等待状态恢复完成
await pwaStateController.waitForStateRestore()
// 将状态管理器绑定到全局对象
;(window as any).pwaStateController = pwaStateController
return pwaStateController
}
return null
}
@@ -125,12 +124,12 @@ if (pwaStateController) {
// 监听状态恢复事件
window.addEventListener('pwaStateRestored', (event: Event) => {
const customEvent = event as CustomEvent
// 可以在这里添加状态恢复后的处理逻辑
// 例如通知Vue组件状态已恢复
app.config.globalProperties.$pwaStateRestored = true
})
// 监听应用即将卸载事件,保存状态
window.addEventListener('beforeunload', () => {
if (pwaStateController) {