优化 PWA 安装提示组件

This commit is contained in:
jxxghp
2025-07-07 23:18:32 +08:00
parent 3ce7fc34f0
commit 5f82cc715e
6 changed files with 297 additions and 104 deletions

View File

@@ -3,12 +3,7 @@ import { usePWAInstall } from '@/composables/usePWAInstall'
import { useToast } from 'vue-toastification'
const { t } = useI18n()
const {
isInstallable,
isInstalled,
showInstallPrompt,
getInstallInstructions
} = usePWAInstall()
const { isInstallable, isInstalled, showInstallPrompt, getInstallInstructions } = usePWAInstall()
const showBanner = ref(false)
const showInstructions = ref(false)
@@ -28,14 +23,14 @@ onMounted(() => {
const dismissedDate = new Date(dismissedTime)
const now = new Date()
const daysDiff = (now.getTime() - dismissedDate.getTime()) / (1000 * 60 * 60 * 24)
// 如果距离上次关闭不到7天不显示
if (daysDiff < 7) {
dismissed.value = true
return
}
}
showBanner.value = true
}, 30000) // 30秒后显示
})
@@ -62,7 +57,21 @@ const dismissBanner = () => {
}
// 获取平台特定的安装说明
const instructions = computed(() => getInstallInstructions())
const instructions = computed(() => {
const rawInstructions = getInstallInstructions()
const platformKey = rawInstructions.platformKey
// 获取平台显示名称
const platformName = t(`pwa.platforms.${platformKey}`)
// 获取安装步骤
const steps = t(`pwa.installSteps.${platformKey}`)
return {
platform: platformName,
steps: Array.isArray(steps) ? steps : [],
}
})
</script>
<template>
@@ -75,30 +84,17 @@ const instructions = computed(() => getInstallInstructions())
leave-from-class="translate-y-0 opacity-100"
leave-to-class="translate-y-full opacity-0"
>
<div
v-if="shouldShowBanner && showBanner"
class="pwa-install-banner"
>
<div v-if="shouldShowBanner && showBanner" class="pwa-install-banner">
<div class="banner-content">
<VIcon icon="mdi-cellphone-link" size="24" class="me-3" />
<div class="flex-grow-1">
<div class="font-weight-medium">安装 MoviePilot 应用</div>
<div class="text-sm opacity-70">获得更好的离线体验和性能</div>
<div class="font-weight-medium">{{ t('pwa.installApp') }}</div>
<div class="text-sm opacity-70">{{ t('pwa.installDescription') }}</div>
</div>
<VBtn
color="primary"
size="small"
variant="flat"
@click="handleInstall"
>
安装
<VBtn color="primary" size="small" variant="flat" @click="handleInstall">
{{ t('pwa.install') }}
</VBtn>
<VBtn
icon
size="small"
variant="text"
@click="dismissBanner"
>
<VBtn icon size="small" variant="text" @click="dismissBanner">
<VIcon icon="mdi-close" />
</VBtn>
</div>
@@ -106,20 +102,17 @@ const instructions = computed(() => getInstallInstructions())
</Transition>
<!-- 手动安装说明对话框 -->
<VDialog
v-model="showInstructions"
max-width="500"
>
<VDialog v-model="showInstructions" max-width="500">
<VCard>
<VCardTitle class="d-flex align-center">
<VIcon icon="mdi-information-outline" class="me-2" />
安装指南
{{ t('pwa.installGuide') }}
</VCardTitle>
<VCardText>
<div class="mb-4">
<div class="text-subtitle-1 mb-2">
{{ instructions.platform }} 上安装 MoviePilot
{{ t('pwa.installInstructions', { platform: instructions.platform }) }}
</div>
<VList density="compact">
<VListItem
@@ -131,24 +124,16 @@ const instructions = computed(() => getInstallInstructions())
</VListItem>
</VList>
</div>
<VAlert
type="info"
variant="tonal"
density="compact"
>
安装后您可以从主屏幕快速访问 MoviePilot并享受离线功能
<VAlert type="info" variant="tonal" density="compact">
{{ t('pwa.installNote') }}
</VAlert>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn
color="primary"
variant="text"
@click="showInstructions = false"
>
知道了
<VBtn color="primary" variant="text" @click="showInstructions = false">
{{ t('pwa.gotIt') }}
</VBtn>
</VCardActions>
</VCard>
@@ -158,14 +143,13 @@ const instructions = computed(() => getInstallInstructions())
<style scoped>
.pwa-install-banner {
position: fixed;
bottom: 20px;
left: 20px;
right: 20px;
z-index: 1000;
background: rgb(var(--v-theme-surface));
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 12px;
background: rgb(var(--v-theme-surface));
box-shadow: 0 4px 20px rgba(0, 0, 0, 10%);
inset-block-end: 20px;
inset-inline: 20px;
}
.banner-content {
@@ -175,11 +159,10 @@ const instructions = computed(() => getInstallInstructions())
gap: 8px;
}
@media (min-width: 600px) {
@media (width >= 600px) {
.pwa-install-banner {
left: auto;
right: 20px;
max-width: 400px;
inset-inline: auto 20px;
max-inline-size: 400px;
}
}
</style>

View File

@@ -25,10 +25,10 @@ export function usePWAInstall() {
const isFullscreen = window.matchMedia('(display-mode: fullscreen)').matches
const isMinimalUI = window.matchMedia('(display-mode: minimal-ui)').matches
const isWindowControlsOverlay = window.matchMedia('(display-mode: window-controls-overlay)').matches
// iOS Safari特殊检查
const isIOSStandalone = (window.navigator as any).standalone === true
return isStandalone || isFullscreen || isMinimalUI || isWindowControlsOverlay || isIOSStandalone
}
@@ -42,18 +42,18 @@ export function usePWAInstall() {
try {
// 显示浏览器的安装提示
await installPrompt.value.prompt()
// 等待用户响应
const { outcome } = await installPrompt.value.userChoice
installOutcome.value = outcome
// 如果用户接受安装,清除安装提示
if (outcome === 'accepted') {
isInstallable.value = false
installPrompt.value = null
isInstalled.value = true
}
return outcome === 'accepted'
} catch (error) {
console.error('Failed to show install prompt:', error)
@@ -65,7 +65,7 @@ export function usePWAInstall() {
const handleBeforeInstallPrompt = (e: BeforeInstallPromptEvent) => {
// 阻止默认行为
e.preventDefault()
// 保存安装提示
installPrompt.value = e
isInstallable.value = true
@@ -103,48 +103,48 @@ export function usePWAInstall() {
if (isIOS && isSafari) {
return {
platform: 'iOS Safari',
steps: [
'点击浏览器底部的分享按钮',
'向下滑动并点击"添加到主屏幕"',
'点击右上角的"添加"',
],
platform: 'ios',
platformKey: 'ios',
}
} else if (isAndroid && isChrome) {
return {
platform: 'Android Chrome',
steps: [
'点击浏览器右上角的菜单按钮(三个点)',
'选择"添加到主屏幕"',
'点击"添加"确认',
],
platform: 'android',
platformKey: 'android',
}
} else if (isEdge) {
return {
platform: 'Microsoft Edge',
steps: [
'点击地址栏右侧的安装按钮',
'或点击菜单中的"应用" > "安装此站点"',
'点击"安装"确认',
],
platform: 'edge',
platformKey: 'edge',
}
} else if (isFirefox && isAndroid) {
return {
platform: 'Firefox Android',
steps: [
'点击浏览器右上角的菜单按钮',
'选择"安装"',
'点击"添加到主屏幕"',
],
platform: 'firefox',
platformKey: 'android', // Firefox on Android uses similar steps to Chrome
}
} else if (isFirefox) {
return {
platform: 'firefox',
platformKey: 'firefox',
}
} else if (isChrome) {
return {
platform: 'chrome',
platformKey: 'chrome',
}
} else if (isSafari) {
return {
platform: 'safari',
platformKey: 'safari',
}
} else if (isAndroid) {
return {
platform: 'mobile',
platformKey: 'mobile',
}
} else {
return {
platform: '您的浏览器',
steps: [
'查看浏览器的菜单或设置',
'寻找"安装应用"或"添加到主屏幕"选项',
'按照提示完成安装',
],
platform: 'desktop',
platformKey: 'desktop',
}
}
}
@@ -152,16 +152,16 @@ export function usePWAInstall() {
onMounted(() => {
// 检查是否已安装
isInstalled.value = checkIfInstalled()
// 监听安装提示事件
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
// 监听安装成功事件
window.addEventListener('appinstalled', handleAppInstalled)
// 监听display-mode变化
const mediaQuery = window.matchMedia('(display-mode: standalone)')
mediaQuery.addEventListener('change', (e) => {
mediaQuery.addEventListener('change', e => {
isInstalled.value = e.matches
})
})
@@ -179,4 +179,4 @@ export function usePWAInstall() {
showInstallPrompt,
getInstallInstructions,
}
}
}

View File

@@ -50,7 +50,7 @@ export function useRecentPlugins() {
// 按访问时间倒序排列
return recentPlugins.sort((a, b) => b.access_time - a.access_time).map(recentPluginToPlugin)
} catch (error) {
console.error('获取最近访问插件失败:', error)
console.error(error)
return []
}
}
@@ -76,7 +76,7 @@ export function useRecentPlugins() {
localStorage.setItem(RECENT_PLUGINS_KEY, JSON.stringify(recentPlugins))
} catch (error) {
console.error('保存最近访问插件失败:', error)
console.error(error)
}
}
@@ -85,7 +85,7 @@ export function useRecentPlugins() {
try {
localStorage.removeItem(RECENT_PLUGINS_KEY)
} catch (error) {
console.error('清除最近访问插件失败:', error)
console.error(error)
}
}
@@ -100,7 +100,7 @@ export function useRecentPlugins() {
localStorage.setItem(RECENT_PLUGINS_KEY, JSON.stringify(recentPlugins))
} catch (error) {
console.error('移除最近访问插件失败:', error)
console.error(error)
}
}

View File

@@ -148,6 +148,76 @@ export default {
online: 'Online Mode',
onlineMessage: 'Network connection restored',
},
pwa: {
installApp: 'Install MoviePilot App',
installDescription: 'Get better offline experience and performance',
install: 'Install',
installSuccess: 'App installed successfully!',
installGuide: 'Installation Guide',
installInstructions: 'Install MoviePilot on {platform}:',
installNote: 'After installation, you can quickly access MoviePilot from your home screen and enjoy offline features.',
gotIt: 'Got it',
// Platform specific descriptions
platforms: {
ios: 'iOS',
android: 'Android',
chrome: 'Chrome',
edge: 'Edge',
firefox: 'Firefox',
safari: 'Safari',
desktop: 'Desktop',
mobile: 'Mobile',
other: 'Other Browser',
},
// Installation steps
installSteps: {
ios: [
'Tap the share button at the bottom of the browser',
'Select "Add to Home Screen"',
'Tap "Add" to confirm installation',
],
android: [
'Tap the browser menu (three dots)',
'Select "Add to Home Screen" or "Install App"',
'Tap "Install" to confirm',
],
chrome: [
'Click the install icon in the address bar',
'Or click "Install MoviePilot" in the browser menu',
'Click "Install" to confirm',
],
edge: [
'Click the app icon in the address bar',
'Select "Install this site as an app"',
'Click "Install" to confirm',
],
firefox: [
'Click the install icon in the address bar',
'Select "Install"',
'Confirm installation to desktop',
],
safari: [
'Click the share button',
'Select "Add to Home Screen"',
'Tap "Add" to confirm',
],
desktop: [
'Click the install icon in the address bar',
'Select "Install App"',
'Follow the prompts to complete installation',
],
mobile: [
'Tap the browser menu',
'Select "Add to Home Screen"',
'Confirm installation',
],
other: [
'Look for "Install" option in your browser',
'Usually in the address bar or menu',
'Follow the prompts to complete installation',
],
},
},
login: {
wallpapers: 'Wallpapers',
username: 'Username',

View File

@@ -148,6 +148,76 @@ export default {
online: '在线模式',
onlineMessage: '网络连接已恢复',
},
pwa: {
installApp: '安装 MoviePilot 应用',
installDescription: '获得更好的离线体验和性能',
install: '安装',
installSuccess: '应用安装成功!',
installGuide: '安装指南',
installInstructions: '在 {platform} 上安装 MoviePilot',
installNote: '安装后,您可以从主屏幕快速访问 MoviePilot并享受离线功能。',
gotIt: '知道了',
// 平台特定的说明
platforms: {
ios: 'iOS',
android: 'Android',
chrome: 'Chrome',
edge: 'Edge',
firefox: 'Firefox',
safari: 'Safari',
desktop: '桌面设备',
mobile: '移动设备',
other: '其他浏览器',
},
// 安装步骤
installSteps: {
ios: [
'点击浏览器底部的分享按钮',
'选择"添加到主屏幕"',
'点击"添加"确认安装',
],
android: [
'点击浏览器菜单(三个点)',
'选择"添加到主屏幕"或"安装应用"',
'点击"安装"确认',
],
chrome: [
'点击地址栏右侧的安装图标',
'或者点击浏览器菜单中的"安装 MoviePilot"',
'点击"安装"确认',
],
edge: [
'点击地址栏右侧的应用图标',
'选择"安装此站点为应用"',
'点击"安装"确认',
],
firefox: [
'点击地址栏右侧的安装图标',
'选择"安装"',
'确认安装到桌面',
],
safari: [
'点击分享按钮',
'选择"添加到主屏幕"',
'点击"添加"确认',
],
desktop: [
'点击地址栏右侧的安装图标',
'选择"安装应用"',
'按照提示完成安装',
],
mobile: [
'点击浏览器菜单',
'选择"添加到主屏幕"',
'确认安装',
],
other: [
'查找浏览器中的"安装"选项',
'通常在地址栏或菜单中',
'按照提示完成安装',
],
},
},
login: {
wallpapers: '壁纸',
username: '用户名',

View File

@@ -149,6 +149,76 @@ export default {
online: '在線模式',
onlineMessage: '網絡連接已恢復',
},
pwa: {
installApp: '安裝 MoviePilot 應用',
installDescription: '獲得更好的離線體驗和性能',
install: '安裝',
installSuccess: '應用安裝成功!',
installGuide: '安裝指南',
installInstructions: '在 {platform} 上安裝 MoviePilot',
installNote: '安裝後,您可以從主屏幕快速訪問 MoviePilot並享受離線功能。',
gotIt: '知道了',
// 平台特定的說明
platforms: {
ios: 'iOS',
android: 'Android',
chrome: 'Chrome',
edge: 'Edge',
firefox: 'Firefox',
safari: 'Safari',
desktop: '桌面設備',
mobile: '移動設備',
other: '其他瀏覽器',
},
// 安裝步驟
installSteps: {
ios: [
'點擊瀏覽器底部的分享按鈕',
'選擇"添加到主屏幕"',
'點擊"添加"確認安裝',
],
android: [
'點擊瀏覽器菜單(三個點)',
'選擇"添加到主屏幕"或"安裝應用"',
'點擊"安裝"確認',
],
chrome: [
'點擊地址欄右側的安裝圖標',
'或者點擊瀏覽器菜單中的"安裝 MoviePilot"',
'點擊"安裝"確認',
],
edge: [
'點擊地址欄右側的應用圖標',
'選擇"安裝此站點為應用"',
'點擊"安裝"確認',
],
firefox: [
'點擊地址欄右側的安裝圖標',
'選擇"安裝"',
'確認安裝到桌面',
],
safari: [
'點擊分享按鈕',
'選擇"添加到主屏幕"',
'點擊"添加"確認',
],
desktop: [
'點擊地址欄右側的安裝圖標',
'選擇"安裝應用"',
'按照提示完成安裝',
],
mobile: [
'點擊瀏覽器菜單',
'選擇"添加到主屏幕"',
'確認安裝',
],
other: [
'查找瀏覽器中的"安裝"選項',
'通常在地址欄或菜單中',
'按照提示完成安裝',
],
},
},
login: {
wallpapers: '壁紙',
username: '用戶名',