mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-14 01:55:57 +08:00
Remove console logs for silent PWA state restoration optimization
Co-authored-by: jxxghp <jxxghp@163.com>
This commit is contained in:
@@ -1,180 +0,0 @@
|
||||
# PWA 后台切换前台体验优化 - 修改总结
|
||||
|
||||
## 已完成的修改
|
||||
|
||||
### 1. 优化PWA状态管理器 (`src/utils/pwaStateManager.ts`)
|
||||
|
||||
#### 1.1 增强PWAStateController类
|
||||
- 添加了状态恢复等待机制 (`waitForStateRestore()`)
|
||||
- 添加了状态恢复状态跟踪 (`isRestoringState`)
|
||||
- 改进了状态恢复流程,确保异步操作完成后才通知应用
|
||||
- 添加了详细的日志记录,便于调试
|
||||
|
||||
#### 1.2 改进VisibilityStateManager类
|
||||
- 添加了轻量级状态恢复指示器
|
||||
- 改进了页面可见性处理逻辑
|
||||
- 添加了防重入机制,避免重复恢复
|
||||
- 优化了状态恢复的用户体验
|
||||
|
||||
### 2. 优化应用初始化 (`src/main.ts`)
|
||||
|
||||
#### 2.1 提前初始化PWA状态管理器
|
||||
- 在Vue应用挂载前初始化PWA状态管理器
|
||||
- 等待状态恢复完成后再挂载应用
|
||||
- 确保PWA状态在应用启动时就已经准备就绪
|
||||
|
||||
#### 2.2 改进事件监听器设置
|
||||
- 优化了状态恢复事件监听
|
||||
- 移除了重复的初始化逻辑
|
||||
- 简化了代码结构
|
||||
|
||||
### 3. 优化加载界面管理 (`src/App.vue`)
|
||||
|
||||
#### 3.1 智能加载界面移除
|
||||
- 创建了`removeLoadingWithStateCheck()`函数
|
||||
- 确保PWA状态恢复完成后才移除加载界面
|
||||
- 添加了多重检查机制,包括全局设置和背景图片加载
|
||||
|
||||
#### 3.2 改进加载动画
|
||||
- 添加了更平滑的加载完成动画
|
||||
- 优化了过渡效果
|
||||
- 添加了PWA状态恢复的视觉反馈
|
||||
|
||||
#### 3.3 增强样式和过渡效果
|
||||
- 添加了PWA过渡效果类
|
||||
- 优化了加载完成动画
|
||||
- 改进了用户体验
|
||||
|
||||
### 4. 优化Service Worker缓存策略 (`vite.config.ts`)
|
||||
|
||||
#### 4.1 启用导航预加载
|
||||
- 在Workbox配置中启用了导航预加载
|
||||
- 提高了页面加载性能
|
||||
|
||||
#### 4.2 改进缓存策略
|
||||
- 将页面缓存策略从`NetworkFirst`改为`StaleWhileRevalidate`
|
||||
- 添加了缓存键优化,提高缓存命中率
|
||||
- 忽略状态参数,避免缓存污染
|
||||
|
||||
### 5. 增强Service Worker功能 (`src/service-worker.ts`)
|
||||
|
||||
#### 5.1 添加状态预缓存
|
||||
- 在Service Worker安装时预缓存关键状态数据
|
||||
- 添加了错误处理机制
|
||||
|
||||
#### 5.2 改进激活事件
|
||||
- 启用导航预加载功能
|
||||
- 添加了缓存清理逻辑
|
||||
- 优化了Service Worker生命周期管理
|
||||
|
||||
### 6. 创建加载状态管理器 (`src/utils/loadingStateManager.ts`)
|
||||
|
||||
#### 6.1 智能加载状态协调
|
||||
- 创建了`PWALoadingStateManager`类
|
||||
- 提供了统一的加载状态管理
|
||||
- 支持多组件加载状态协调
|
||||
|
||||
#### 6.2 加载状态监听
|
||||
- 添加了状态变化监听器
|
||||
- 提供了等待所有加载完成的机制
|
||||
- 支持状态重置和详细状态查询
|
||||
|
||||
## 关键特性
|
||||
|
||||
### 1. 状态恢复等待机制
|
||||
- 应用在PWA状态恢复完成前不会显示
|
||||
- 避免了用户看到不一致的状态
|
||||
- 确保了平滑的用户体验
|
||||
|
||||
### 2. 轻量级状态恢复指示器
|
||||
- 在页面可见性切换时显示轻量级指示器
|
||||
- 避免了重新显示完整的加载界面
|
||||
- 提供了即时的用户反馈
|
||||
|
||||
### 3. 智能缓存策略
|
||||
- 优化了页面和资源的缓存策略
|
||||
- 启用了导航预加载功能
|
||||
- 提高了应用响应速度
|
||||
|
||||
### 4. 详细的日志记录
|
||||
- 添加了全面的日志记录
|
||||
- 便于调试和性能监控
|
||||
- 提供了状态恢复过程的可见性
|
||||
|
||||
## 预期效果
|
||||
|
||||
### 1. 消除界面闪烁
|
||||
- ✅ 状态恢复完成后才显示界面
|
||||
- ✅ 避免了用户看到不一致的状态
|
||||
- ✅ 提供了平滑的过渡效果
|
||||
|
||||
### 2. 提升加载性能
|
||||
- ✅ 智能缓存策略减少了网络请求
|
||||
- ✅ 导航预加载提高了响应速度
|
||||
- ✅ 多重加载状态协调确保了资源就绪
|
||||
|
||||
### 3. 改善用户体验
|
||||
- ✅ 添加了过渡动画和视觉反馈
|
||||
- ✅ 轻量级指示器替代了完整加载界面
|
||||
- ✅ 状态恢复过程更加透明
|
||||
|
||||
### 4. 接近原生体验
|
||||
- ✅ 快速的状态恢复
|
||||
- ✅ 平滑的切换动画
|
||||
- ✅ 即时的用户反馈
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 开发环境测试
|
||||
1. 启动开发服务器:`npm run dev`
|
||||
2. 在浏览器中打开应用
|
||||
3. 使用开发者工具测试PWA功能
|
||||
4. 检查控制台日志,确认状态恢复过程
|
||||
|
||||
### 2. 真机测试
|
||||
1. 构建生产版本:`npm run build`
|
||||
2. 部署到服务器或使用本地服务器
|
||||
3. 在移动设备上安装PWA
|
||||
4. 测试后台切换前台的各种场景
|
||||
|
||||
### 3. 性能监控
|
||||
- 使用Chrome DevTools监控性能
|
||||
- 检查状态恢复时间
|
||||
- 监控内存使用情况
|
||||
- 验证缓存策略效果
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 兼容性
|
||||
- 确保在不同浏览器上测试
|
||||
- 特别关注iOS Safari和Android Chrome
|
||||
- 测试不同版本的Service Worker支持
|
||||
|
||||
### 2. 性能
|
||||
- 监控状态恢复的性能影响
|
||||
- 避免过度的状态保存
|
||||
- 定期清理过期的状态数据
|
||||
|
||||
### 3. 用户体验
|
||||
- 确保状态恢复不会影响用户操作
|
||||
- 提供适当的加载反馈
|
||||
- 处理网络异常情况
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
### 1. 高级功能
|
||||
- 添加骨架屏支持
|
||||
- 实现智能状态压缩
|
||||
- 添加状态恢复统计
|
||||
|
||||
### 2. 性能优化
|
||||
- 实现增量状态更新
|
||||
- 优化状态序列化
|
||||
- 添加状态恢复缓存
|
||||
|
||||
### 3. 用户体验
|
||||
- 添加自定义状态恢复动画
|
||||
- 实现状态恢复进度指示
|
||||
- 支持用户自定义状态恢复偏好
|
||||
|
||||
这些修改将显著提升PWA应用从后台切换到前台时的用户体验,使其更接近原生应用的表现。
|
||||
@@ -1,409 +0,0 @@
|
||||
# PWA 后台切换前台体验优化分析报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
从后台切换到前台时会显示加载界面,同时界面会有闪烁,切换的体验不是很好,跟原生APP还有差距。
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 1. 核心问题原因
|
||||
|
||||
#### 1.1 状态恢复时机不当
|
||||
- **PWA状态管理器初始化延迟**:在 `main.ts` 中,PWA状态管理器的初始化是在Vue应用挂载后进行的
|
||||
- **异步状态恢复**:状态恢复是异步的,不会阻塞应用渲染,导致用户先看到初始状态
|
||||
- **渲染完成检测不准确**:`ensureRenderComplete` 只检测Vue组件渲染完成,未考虑PWA状态恢复
|
||||
|
||||
#### 1.2 加载界面移除时机过早
|
||||
```typescript
|
||||
// 在App.vue中的问题代码
|
||||
ensureRenderComplete(() => {
|
||||
nextTick(() => {
|
||||
// 移除加载动画,显示页面
|
||||
animateAndRemoveLoader()
|
||||
// 页面完全显示后,检查未读消息
|
||||
checkAndEmitUnreadMessages()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
加载界面在Vue组件渲染完成后立即移除,但此时:
|
||||
- PWA状态尚未恢复
|
||||
- 用户界面状态可能不完整
|
||||
- 导致用户看到不一致的状态
|
||||
|
||||
#### 1.3 Service Worker状态同步延迟
|
||||
Service Worker中的状态恢复通过消息传递实现,存在延迟:
|
||||
```typescript
|
||||
// 在service-worker.ts中
|
||||
self.addEventListener('message', function (event) {
|
||||
if (event.data && event.data.type === 'GET_PWA_STATE') {
|
||||
getStateFromCache()
|
||||
.then(response => response.json())
|
||||
.then(state => {
|
||||
event.ports[0]?.postMessage({ state })
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 具体表现
|
||||
|
||||
1. **界面闪烁**:
|
||||
- 用户首先看到默认状态
|
||||
- 然后看到状态恢复过程
|
||||
- 最后看到完整的恢复状态
|
||||
|
||||
2. **加载界面显示**:
|
||||
- 从后台切换到前台时,某些组件可能需要重新渲染
|
||||
- 背景图片需要重新加载
|
||||
- 网络请求需要重新发起
|
||||
|
||||
3. **状态不一致**:
|
||||
- 滚动位置不对
|
||||
- 表单数据丢失
|
||||
- 用户选择状态丢失
|
||||
|
||||
## 解决方案建议
|
||||
|
||||
### 1. 优化状态恢复时机
|
||||
|
||||
#### 1.1 提前初始化PWA状态管理器
|
||||
```typescript
|
||||
// 修改main.ts,在应用挂载前初始化
|
||||
const initializePWABeforeMount = async () => {
|
||||
const isPWA = window.matchMedia('(display-mode: standalone)').matches ||
|
||||
(window.navigator as any).standalone ||
|
||||
document.referrer.includes('android-app://')
|
||||
|
||||
if (isPWA) {
|
||||
console.log('检测到PWA模式,预初始化状态管理器')
|
||||
const pwaStateController = new PWAStateController()
|
||||
|
||||
// 等待状态恢复完成
|
||||
await pwaStateController.waitForStateRestore()
|
||||
|
||||
// 将状态管理器绑定到全局对象
|
||||
;(window as any).pwaStateController = pwaStateController
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 在创建Vue应用前调用
|
||||
const pwaInitialized = await initializePWABeforeMount()
|
||||
const app = createApp(App)
|
||||
```
|
||||
|
||||
#### 1.2 实现状态恢复等待机制
|
||||
```typescript
|
||||
// 在PWAStateController中添加
|
||||
export class PWAStateController {
|
||||
private stateRestorePromise: Promise<void> | null = null
|
||||
private stateRestoreResolve: (() => void) | null = null
|
||||
|
||||
constructor() {
|
||||
this.stateRestorePromise = new Promise((resolve) => {
|
||||
this.stateRestoreResolve = resolve
|
||||
})
|
||||
this.init()
|
||||
}
|
||||
|
||||
async waitForStateRestore(): Promise<void> {
|
||||
return this.stateRestorePromise
|
||||
}
|
||||
|
||||
private async checkAndRestoreState(): Promise<void> {
|
||||
// 现有的状态恢复逻辑
|
||||
// ...
|
||||
|
||||
// 状态恢复完成后解决Promise
|
||||
if (this.stateRestoreResolve) {
|
||||
this.stateRestoreResolve()
|
||||
this.stateRestoreResolve = null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 优化加载界面管理
|
||||
|
||||
#### 2.1 条件化加载界面移除
|
||||
```typescript
|
||||
// 修改App.vue中的加载界面移除逻辑
|
||||
const removeLoadingWithStateCheck = async () => {
|
||||
// 检查PWA状态是否已恢复
|
||||
const pwaController = (window as any).pwaStateController
|
||||
if (pwaController) {
|
||||
await pwaController.waitForStateRestore()
|
||||
}
|
||||
|
||||
// 确保关键资源已加载
|
||||
await Promise.all([
|
||||
// 等待背景图片加载完成
|
||||
loadBackgroundImages(),
|
||||
// 等待关键API数据加载完成
|
||||
loadCriticalData()
|
||||
])
|
||||
|
||||
// 移除加载界面
|
||||
animateAndRemoveLoader()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await globalSettingsStore.initialize()
|
||||
configureApexCharts()
|
||||
updateHtmlThemeAttribute(globalTheme.name.value)
|
||||
|
||||
// 使用优化后的加载界面移除逻辑
|
||||
ensureRenderComplete(() => {
|
||||
nextTick(removeLoadingWithStateCheck)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### 2.2 实现智能加载状态检测
|
||||
```typescript
|
||||
// 新增工具函数
|
||||
export class PWALoadingStateManager {
|
||||
private loadingStates: Map<string, boolean> = new Map()
|
||||
|
||||
setLoadingState(key: string, loading: boolean) {
|
||||
this.loadingStates.set(key, loading)
|
||||
}
|
||||
|
||||
isAnyLoading(): boolean {
|
||||
return Array.from(this.loadingStates.values()).some(loading => loading)
|
||||
}
|
||||
|
||||
waitForAllComplete(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const checkComplete = () => {
|
||||
if (!this.isAnyLoading()) {
|
||||
resolve()
|
||||
} else {
|
||||
setTimeout(checkComplete, 100)
|
||||
}
|
||||
}
|
||||
checkComplete()
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 优化页面可见性处理
|
||||
|
||||
#### 3.1 改进页面可见性监听
|
||||
```typescript
|
||||
// 修改VisibilityStateManager
|
||||
export class VisibilityStateManager {
|
||||
private isRestoring = false
|
||||
private restorePromise: Promise<void> | null = null
|
||||
|
||||
private handlePageVisible(): void {
|
||||
if (this.isRestoring) return
|
||||
|
||||
this.isRestoring = true
|
||||
this.restorePromise = this.performStateRestore()
|
||||
}
|
||||
|
||||
private async performStateRestore(): Promise<void> {
|
||||
try {
|
||||
// 显示恢复指示器
|
||||
this.showRestoreIndicator()
|
||||
|
||||
const restoredState = this.stateManager.restoreState()
|
||||
if (restoredState) {
|
||||
await this.restoreAppState(restoredState)
|
||||
console.log('页面显示,已恢复状态')
|
||||
}
|
||||
} finally {
|
||||
this.isRestoring = false
|
||||
this.hideRestoreIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
private showRestoreIndicator(): void {
|
||||
// 显示轻量级的状态恢复指示器,而不是完整的加载界面
|
||||
const indicator = document.createElement('div')
|
||||
indicator.id = 'pwa-restore-indicator'
|
||||
indicator.innerHTML = `
|
||||
<div class="restore-indicator">
|
||||
<div class="restore-spinner"></div>
|
||||
<div class="restore-text">正在恢复状态...</div>
|
||||
</div>
|
||||
`
|
||||
document.body.appendChild(indicator)
|
||||
}
|
||||
|
||||
private hideRestoreIndicator(): void {
|
||||
const indicator = document.getElementById('pwa-restore-indicator')
|
||||
if (indicator) {
|
||||
indicator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 优化Service Worker缓存策略
|
||||
|
||||
#### 4.1 改进缓存预热
|
||||
```typescript
|
||||
// 修改vite.config.ts中的PWA配置
|
||||
VitePWA({
|
||||
workbox: {
|
||||
// 添加导航预加载
|
||||
navigationPreload: true,
|
||||
// 优化缓存策略
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: ({ request }) => request.destination === 'document',
|
||||
handler: 'StaleWhileRevalidate', // 改为更快的策略
|
||||
options: {
|
||||
cacheName: 'pages-cache',
|
||||
cacheKeyWillBeUsed: async ({ request }) => {
|
||||
// 忽略状态参数,提高缓存命中率
|
||||
const url = new URL(request.url)
|
||||
url.searchParams.delete('restored')
|
||||
return url.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 4.2 实现状态预缓存
|
||||
```typescript
|
||||
// 在service-worker.ts中添加
|
||||
self.addEventListener('install', event => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
// 预缓存关键状态数据
|
||||
const cache = await caches.open(STATE_CACHE_NAME)
|
||||
const existingState = await cache.match(STATE_ENDPOINT)
|
||||
|
||||
if (existingState) {
|
||||
// 预热状态数据
|
||||
const state = await existingState.json()
|
||||
console.log('预缓存状态数据:', state)
|
||||
}
|
||||
})()
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
### 5. 增强用户体验
|
||||
|
||||
#### 5.1 添加过渡动画
|
||||
```scss
|
||||
// 添加到App.vue的样式中
|
||||
.pwa-transition {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.pwa-restoring {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.restore-indicator {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10000;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.restore-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid transparent;
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 实现骨架屏
|
||||
```vue
|
||||
<!-- 添加到App.vue中 -->
|
||||
<template>
|
||||
<div class="app-wrapper">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="isRestoring" class="skeleton-screen">
|
||||
<div class="skeleton-header"></div>
|
||||
<div class="skeleton-content">
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line short"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<VApp v-else :class="{ 'transparent-app': isTransparentTheme }">
|
||||
<RouterView />
|
||||
</VApp>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 实施建议
|
||||
|
||||
### 优先级排序
|
||||
|
||||
1. **高优先级**:
|
||||
- 优化状态恢复时机
|
||||
- 改进加载界面管理
|
||||
- 实现状态恢复等待机制
|
||||
|
||||
2. **中优先级**:
|
||||
- 优化页面可见性处理
|
||||
- 添加过渡动画
|
||||
- 改进Service Worker缓存策略
|
||||
|
||||
3. **低优先级**:
|
||||
- 实现骨架屏
|
||||
- 添加高级用户体验功能
|
||||
|
||||
### 测试建议
|
||||
|
||||
1. **真机测试**:
|
||||
- 在iOS Safari上测试PWA模式
|
||||
- 在Android Chrome上测试PWA模式
|
||||
- 测试后台切换前台的各种场景
|
||||
|
||||
2. **性能测试**:
|
||||
- 使用Chrome DevTools监控状态恢复性能
|
||||
- 测试不同网络状况下的表现
|
||||
- 监控内存使用情况
|
||||
|
||||
3. **用户体验测试**:
|
||||
- 测试快速切换应用场景
|
||||
- 测试长时间后台后的恢复
|
||||
- 测试网络断开重连场景
|
||||
|
||||
## 预期效果
|
||||
|
||||
实施以上优化后,预期能够:
|
||||
|
||||
1. **消除界面闪烁**:状态恢复完成后再显示界面
|
||||
2. **减少加载时间**:智能预加载和缓存策略
|
||||
3. **提升用户体验**:平滑的过渡动画和反馈
|
||||
4. **接近原生体验**:快速的状态恢复和响应
|
||||
|
||||
这些优化将显著提升PWA应用从后台切换到前台时的体验,使其更接近原生应用的表现。
|
||||
@@ -1,207 +0,0 @@
|
||||
# PWA 静默优化总结 - 完全无感的后台切换体验
|
||||
|
||||
## 优化目标
|
||||
|
||||
实现PWA应用从后台切换到前台时的**完全无感体验**,不显示任何指示器或加载界面,让用户感觉不到任何延迟或状态恢复过程。
|
||||
|
||||
## 核心优化策略
|
||||
|
||||
### 1. 静默状态恢复
|
||||
- ✅ **移除所有视觉指示器**:不显示任何状态恢复提示
|
||||
- ✅ **后台静默处理**:状态恢复在后台完全静默进行
|
||||
- ✅ **即时响应**:用户操作不会被阻塞或延迟
|
||||
|
||||
### 2. 智能状态管理
|
||||
- ✅ **宽松的恢复策略**:即使URL不完全匹配也尝试恢复合适的状态
|
||||
- ✅ **选择性状态恢复**:只在合适的情况下恢复特定状态(如滚动位置)
|
||||
- ✅ **延长状态有效期**:从30分钟延长到60分钟
|
||||
|
||||
### 3. 性能优化
|
||||
- ✅ **减少延迟**:将等待时间从200ms减少到50ms
|
||||
- ✅ **并行处理**:多个资源并行加载
|
||||
- ✅ **静默错误处理**:错误不会干扰用户体验
|
||||
|
||||
## 具体实现
|
||||
|
||||
### 1. 移除视觉反馈 (`src/utils/pwaStateManager.ts`)
|
||||
```typescript
|
||||
// 移除了指示器显示逻辑
|
||||
private async performStateRestore(): Promise<void> {
|
||||
try {
|
||||
const restoredState = this.stateManager.restoreState()
|
||||
if (restoredState) {
|
||||
await this.restoreAppState(restoredState)
|
||||
console.log('页面显示,已静默恢复状态')
|
||||
}
|
||||
} finally {
|
||||
this.isRestoring = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 优化状态恢复决策
|
||||
```typescript
|
||||
export class StateRestoreDecision {
|
||||
private maxStateAge = 60 * 60 * 1000 // 延长到60分钟
|
||||
|
||||
shouldRestoreState(savedState: PWAState | null, currentContext: PWAContext): boolean {
|
||||
// 更宽松的匹配策略
|
||||
if (!this.isUrlCompatible(savedState.url, currentContext.url)) {
|
||||
// 即使URL不匹配,也恢复基础状态
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 智能状态过滤
|
||||
```typescript
|
||||
private async restoreState(state: PWAState): Promise<void> {
|
||||
const urlMatches = this.isUrlExactMatch(state.url, currentUrl)
|
||||
|
||||
// 只有URL完全匹配时才恢复滚动位置
|
||||
if (state.scrollPosition && urlMatches) {
|
||||
window.scrollTo({ top: state.scrollPosition, behavior: 'auto' })
|
||||
}
|
||||
|
||||
// 过滤状态恢复
|
||||
if (state.appData) {
|
||||
this.restoreAppSpecificState(state.appData, urlMatches)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 移除UI状态跟踪 (`src/App.vue`)
|
||||
```typescript
|
||||
// 移除了状态跟踪变量
|
||||
// const isRestoring = ref(false) // 已删除
|
||||
|
||||
// 移除了视觉反馈类
|
||||
// <VApp :class="{ 'transparent-app': isTransparentTheme }"> // 简化
|
||||
```
|
||||
|
||||
### 5. 优化加载检查
|
||||
```typescript
|
||||
async function removeLoadingWithStateCheck() {
|
||||
// 静默检查PWA状态恢复
|
||||
const pwaController = (window as any).pwaStateController
|
||||
if (pwaController) {
|
||||
await pwaController.waitForStateRestore() // 无任何UI反馈
|
||||
}
|
||||
|
||||
// 并行加载,减少延迟
|
||||
await Promise.all([
|
||||
globalSettingsStore.initialize(),
|
||||
new Promise(resolve => setTimeout(resolve, 50)) // 50ms vs 原200ms
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
## 用户体验改进
|
||||
|
||||
### 1. 完全无感知
|
||||
- ❌ **无加载指示器**:用户看不到任何状态恢复过程
|
||||
- ❌ **无界面闪烁**:状态恢复不会影响当前界面
|
||||
- ❌ **无延迟感知**:操作响应依然即时
|
||||
|
||||
### 2. 智能恢复
|
||||
- ✅ **适应性强**:即使在不同页面也能恢复合适的状态
|
||||
- ✅ **安全可靠**:错误不会影响正常使用
|
||||
- ✅ **持久有效**:更长的状态保持时间
|
||||
|
||||
### 3. 性能优异
|
||||
- ✅ **快速响应**:减少了不必要的等待时间
|
||||
- ✅ **并行处理**:多任务同时进行
|
||||
- ✅ **资源优化**:只恢复必要的状态
|
||||
|
||||
## 技术特点
|
||||
|
||||
### 1. 静默处理
|
||||
```typescript
|
||||
// 错误静默处理
|
||||
} catch (error) {
|
||||
// 静默处理错误,不输出详细错误信息
|
||||
}
|
||||
|
||||
// 状态恢复静默进行
|
||||
console.log('PWA状态静默恢复成功') // 仅开发调试用
|
||||
```
|
||||
|
||||
### 2. 智能过滤
|
||||
```typescript
|
||||
// 根据URL匹配情况决定恢复内容
|
||||
private restoreAppSpecificState(appData: any, urlMatches: boolean = true): void {
|
||||
// 总是恢复UI状态(如主题等)
|
||||
if (appData.uiState) {
|
||||
this.restoreUIState(appData.uiState)
|
||||
}
|
||||
|
||||
// 只有在URL匹配时才恢复表单状态
|
||||
if (appData.formState && urlMatches) {
|
||||
this.restoreFormState(appData.formState)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 宽松策略
|
||||
- 允许URL不匹配时的状态恢复
|
||||
- 设备方向变化不阻止恢复
|
||||
- 延长状态有效期到60分钟
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 1. 用户体验测试
|
||||
- [x] 后台切换前台无任何指示器
|
||||
- [x] 状态恢复过程完全静默
|
||||
- [x] 用户操作不受影响
|
||||
- [x] 界面无闪烁或跳动
|
||||
|
||||
### 2. 功能测试
|
||||
- [x] 滚动位置正确恢复(同页面)
|
||||
- [x] 主题设置正确保持
|
||||
- [x] 表单数据适当恢复
|
||||
- [x] 跨页面状态处理正确
|
||||
|
||||
### 3. 性能测试
|
||||
- [x] 状态恢复时间 < 100ms
|
||||
- [x] 内存使用保持稳定
|
||||
- [x] 网络请求不增加
|
||||
- [x] 电池消耗无明显影响
|
||||
|
||||
## 兼容性保证
|
||||
|
||||
### 1. 浏览器兼容
|
||||
- ✅ iOS Safari PWA模式
|
||||
- ✅ Android Chrome PWA模式
|
||||
- ✅ 桌面浏览器PWA模式
|
||||
|
||||
### 2. 降级处理
|
||||
- ✅ Service Worker不可用时的备选方案
|
||||
- ✅ 存储不可用时的容错处理
|
||||
- ✅ 网络异常时的静默处理
|
||||
|
||||
## 维护建议
|
||||
|
||||
### 1. 监控指标
|
||||
- 状态恢复成功率
|
||||
- 状态恢复时间
|
||||
- 错误发生频率
|
||||
- 用户满意度
|
||||
|
||||
### 2. 定期优化
|
||||
- 清理过期状态数据
|
||||
- 优化状态序列化大小
|
||||
- 监控性能指标
|
||||
- 收集用户反馈
|
||||
|
||||
## 总结
|
||||
|
||||
通过这次优化,我们实现了:
|
||||
|
||||
1. **完全无感的PWA体验**:用户感觉不到任何状态恢复过程
|
||||
2. **智能状态管理**:根据上下文智能决定恢复策略
|
||||
3. **优异的性能表现**:快速、静默、高效的状态恢复
|
||||
4. **可靠的容错处理**:错误不影响正常使用
|
||||
|
||||
PWA应用现在能够提供接近原生应用的切换体验,用户在后台切换前台时感受不到任何延迟或中断,实现了真正的无感状态恢复。
|
||||
13
src/App.vue
13
src/App.vue
@@ -140,8 +140,6 @@ function animateAndRemoveLoader() {
|
||||
// 检查PWA状态并移除加载界面
|
||||
async function removeLoadingWithStateCheck() {
|
||||
try {
|
||||
console.log('开始静默检查加载状态...')
|
||||
|
||||
// 设置各个组件的加载状态
|
||||
globalLoadingStateManager.setLoadingState('pwa-state', true)
|
||||
globalLoadingStateManager.setLoadingState('global-settings', true)
|
||||
@@ -175,13 +173,12 @@ async function removeLoadingWithStateCheck() {
|
||||
|
||||
// 检查未读消息
|
||||
checkAndEmitUnreadMessages()
|
||||
} catch (error) {
|
||||
console.error('移除加载界面时发生错误:', error)
|
||||
// 即使出错也要移除加载界面
|
||||
globalLoadingStateManager.reset()
|
||||
animateAndRemoveLoader()
|
||||
} catch (error) {
|
||||
// 即使出错也要移除加载界面
|
||||
globalLoadingStateManager.reset()
|
||||
animateAndRemoveLoader()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载背景图片
|
||||
async function loadBackgroundImages(retryCount = 0) {
|
||||
|
||||
@@ -18,14 +18,13 @@ export function usePWAState() {
|
||||
const saveCurrentState = async () => {
|
||||
if (window.pwaStateController) {
|
||||
await window.pwaStateController.saveCurrentState()
|
||||
console.log('手动保存PWA状态')
|
||||
}
|
||||
}
|
||||
|
||||
// 手动触发状态恢复检查
|
||||
const checkStateRestore = async () => {
|
||||
if (window.pwaStateController) {
|
||||
console.log('检查状态恢复')
|
||||
// 静默检查
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +34,6 @@ export function usePWAState() {
|
||||
isStateRestored.value = true
|
||||
stateRestoreCount.value++
|
||||
lastRestoredState.value = customEvent.detail.state
|
||||
|
||||
console.log('Vue组件收到状态恢复通知:', customEvent.detail.state)
|
||||
}
|
||||
|
||||
// 重置状态恢复标志
|
||||
@@ -115,7 +112,6 @@ export function useGlobalPWAState() {
|
||||
const clearStoredState = () => {
|
||||
localStorage.removeItem('mp-pwa-app-state')
|
||||
sessionStorage.removeItem('mp-pwa-session-state')
|
||||
console.log('已清除PWA存储状态')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -54,7 +54,6 @@ const initializePWABeforeMount = async () => {
|
||||
document.referrer.includes('android-app://')
|
||||
|
||||
if (isPWA) {
|
||||
console.log('检测到PWA模式,预初始化状态管理器')
|
||||
const pwaStateController = new PWAStateController()
|
||||
|
||||
// 等待状态恢复完成
|
||||
@@ -124,7 +123,6 @@ if (pwaStateController) {
|
||||
// 监听状态恢复事件
|
||||
window.addEventListener('pwaStateRestored', (event: Event) => {
|
||||
const customEvent = event as CustomEvent
|
||||
console.log('PWA状态已恢复:', customEvent.detail.state)
|
||||
|
||||
// 可以在这里添加状态恢复后的处理逻辑
|
||||
// 例如:通知Vue组件状态已恢复
|
||||
|
||||
@@ -151,7 +151,6 @@ async function clearBadge() {
|
||||
|
||||
// 安装事件
|
||||
self.addEventListener('install', event => {
|
||||
console.log('Service Worker install')
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
// 预缓存关键状态数据
|
||||
@@ -162,10 +161,9 @@ self.addEventListener('install', event => {
|
||||
if (existingState) {
|
||||
// 预热状态数据
|
||||
const state = await existingState.json()
|
||||
console.log('预缓存状态数据:', state)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('预缓存状态数据失败:', error)
|
||||
// 静默处理错误
|
||||
}
|
||||
|
||||
// 强制等待中的Service Worker立即成为活动的Service Worker
|
||||
@@ -176,13 +174,11 @@ self.addEventListener('install', event => {
|
||||
|
||||
// 激活事件
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('Service Worker activate')
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
// 启用导航预载功能以提高性能
|
||||
if ('navigationPreload' in self.registration) {
|
||||
await self.registration.navigationPreload.enable()
|
||||
console.log('导航预加载已启用')
|
||||
}
|
||||
|
||||
// 清理旧版本的缓存
|
||||
@@ -190,7 +186,6 @@ self.addEventListener('activate', event => {
|
||||
await Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
if (cacheName.includes('old-') || cacheName.includes('deprecated-')) {
|
||||
console.log('清理旧缓存:', cacheName)
|
||||
return caches.delete(cacheName)
|
||||
}
|
||||
})
|
||||
@@ -257,7 +252,6 @@ precacheAndRoute(self.__WB_MANIFEST)
|
||||
|
||||
// 监听 push 事件,显示通知
|
||||
self.addEventListener('push', function (event) {
|
||||
console.log('notification push')
|
||||
if (!event.data) {
|
||||
return
|
||||
}
|
||||
@@ -266,7 +260,6 @@ self.addEventListener('push', function (event) {
|
||||
try {
|
||||
payload = event.data?.json()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
payload = {
|
||||
title: event.data?.text(),
|
||||
}
|
||||
@@ -290,14 +283,13 @@ self.addEventListener('push', function (event) {
|
||||
await Promise.all([self.registration.showNotification(payload.title, content), updateBadge(newCount)])
|
||||
})(),
|
||||
)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} catch (e) {
|
||||
// 静默处理错误
|
||||
}
|
||||
})
|
||||
|
||||
// 监听通知点击事件
|
||||
self.addEventListener('notificationclick', function (event) {
|
||||
console.log('notification click')
|
||||
const info = event.notification
|
||||
if (event.action === 'close') {
|
||||
info.close()
|
||||
@@ -308,7 +300,6 @@ self.addEventListener('notificationclick', function (event) {
|
||||
|
||||
// 监听来自主应用的消息,用于清除徽章或更新徽章数量
|
||||
self.addEventListener('message', function (event) {
|
||||
console.log('service worker received message:', event.data)
|
||||
|
||||
if (event.data && event.data.type === 'CLEAR_BADGE') {
|
||||
// 清除徽章
|
||||
@@ -317,7 +308,6 @@ self.addEventListener('message', function (event) {
|
||||
event.ports[0]?.postMessage({ success: true })
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to clear badge:', error)
|
||||
event.ports[0]?.postMessage({ success: false, error: error instanceof Error ? error.message : String(error) })
|
||||
})
|
||||
} else if (event.data && event.data.type === 'UPDATE_BADGE') {
|
||||
@@ -329,7 +319,6 @@ self.addEventListener('message', function (event) {
|
||||
event.ports[0]?.postMessage({ success: true })
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to update badge:', error)
|
||||
event.ports[0]?.postMessage({ success: false, error: error instanceof Error ? error.message : String(error) })
|
||||
})
|
||||
} else if (event.data && event.data.type === 'GET_UNREAD_COUNT') {
|
||||
@@ -339,7 +328,6 @@ self.addEventListener('message', function (event) {
|
||||
event.ports[0]?.postMessage({ count })
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to get unread count:', error)
|
||||
event.ports[0]?.postMessage({ count: 0 })
|
||||
})
|
||||
} else if (event.data && event.data.type === 'SAVE_PWA_STATE') {
|
||||
@@ -355,7 +343,6 @@ self.addEventListener('message', function (event) {
|
||||
event.ports[0]?.postMessage({ success: result.success })
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to save PWA state:', error)
|
||||
event.ports[0]?.postMessage({ success: false, error: error instanceof Error ? error.message : String(error) })
|
||||
})
|
||||
} else if (event.data && event.data.type === 'GET_PWA_STATE') {
|
||||
@@ -366,7 +353,6 @@ self.addEventListener('message', function (event) {
|
||||
event.ports[0]?.postMessage({ state })
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to get PWA state:', error)
|
||||
event.ports[0]?.postMessage({ state: {} })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export class PWALoadingStateManager {
|
||||
try {
|
||||
listener(isLoading)
|
||||
} catch (error) {
|
||||
console.error('加载状态监听器错误:', error)
|
||||
// 静默处理错误
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -312,7 +312,6 @@ export class VisibilityStateManager {
|
||||
private handlePageHidden(): void {
|
||||
const currentState = this.getCurrentAppState()
|
||||
this.stateManager.saveState(currentState)
|
||||
console.log('页面被隐藏,已保存状态')
|
||||
}
|
||||
|
||||
private handlePageVisible(): void {
|
||||
@@ -327,10 +326,9 @@ export class VisibilityStateManager {
|
||||
const restoredState = this.stateManager.restoreState()
|
||||
if (restoredState) {
|
||||
await this.restoreAppState(restoredState)
|
||||
console.log('页面显示,已静默恢复状态')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('状态恢复失败:', error)
|
||||
// 静默处理错误
|
||||
} finally {
|
||||
this.isRestoring = false
|
||||
}
|
||||
@@ -530,11 +528,10 @@ export class PWAStateController {
|
||||
const savedState = await source()
|
||||
if (this.restoreDecision.shouldRestoreState(savedState, currentContext)) {
|
||||
await this.restoreState(savedState!)
|
||||
console.log('PWA状态静默恢复成功')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// 静默处理错误,不输出详细错误信息
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -566,8 +563,6 @@ export class PWAStateController {
|
||||
}
|
||||
|
||||
private async restoreState(state: PWAState): Promise<void> {
|
||||
console.log('开始静默恢复PWA状态')
|
||||
|
||||
const currentUrl = window.location.href
|
||||
const urlMatches = this.isUrlExactMatch(state.url, currentUrl)
|
||||
|
||||
@@ -586,7 +581,6 @@ export class PWAStateController {
|
||||
|
||||
// 触发状态恢复事件
|
||||
this.dispatchStateRestoreEvent(state)
|
||||
console.log('PWA状态静默恢复完成')
|
||||
}
|
||||
|
||||
private isUrlExactMatch(savedUrl: string, currentUrl: string): boolean {
|
||||
@@ -678,7 +672,6 @@ export class PWAStateController {
|
||||
// 恢复UI状态
|
||||
if (uiState.darkMode !== undefined) {
|
||||
// 这里可以根据实际的主题切换逻辑来恢复
|
||||
console.log('恢复主题状态:', uiState.darkMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user