0.0.0.4
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Download, X, Smartphone } from "lucide-react"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
|
||||
export function PWAInstaller() {
|
||||
const [deferredPrompt, setDeferredPrompt] = useState<any>(null)
|
||||
const [isInstallable, setIsInstallable] = useState(false)
|
||||
const [isInstalled, setIsInstalled] = useState(false)
|
||||
const [showPrompt, setShowPrompt] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// 检查是否已安装
|
||||
if (typeof window !== "undefined") {
|
||||
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|
||||
const isIOSStandalone = (window.navigator as any).standalone === true
|
||||
|
||||
if (isStandalone || isIOSStandalone) {
|
||||
setIsInstalled(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 beforeinstallprompt 事件
|
||||
const handleBeforeInstallPrompt = (e: Event) => {
|
||||
e.preventDefault()
|
||||
setDeferredPrompt(e)
|
||||
setIsInstallable(true)
|
||||
|
||||
// 延迟显示提示(给用户一些时间浏览应用)
|
||||
setTimeout(() => {
|
||||
const hasSeenPrompt = localStorage.getItem('pwa-install-prompt-seen')
|
||||
if (!hasSeenPrompt) {
|
||||
setShowPrompt(true)
|
||||
}
|
||||
}, 30000) // 30秒后显示
|
||||
}
|
||||
|
||||
// 监听安装完成事件
|
||||
const handleAppInstalled = () => {
|
||||
setIsInstalled(true)
|
||||
setShowPrompt(false)
|
||||
setIsInstallable(false)
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
||||
window.addEventListener('appinstalled', handleAppInstalled)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
|
||||
window.removeEventListener('appinstalled', handleAppInstalled)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleInstallClick = async () => {
|
||||
if (!deferredPrompt) return
|
||||
|
||||
// 显示安装提示
|
||||
deferredPrompt.prompt()
|
||||
|
||||
// 等待用户响应
|
||||
const { outcome } = await deferredPrompt.userChoice
|
||||
|
||||
if (outcome === 'accepted') {
|
||||
console.log('用户接受了安装')
|
||||
} else {
|
||||
console.log('用户拒绝了安装')
|
||||
}
|
||||
|
||||
setDeferredPrompt(null)
|
||||
setShowPrompt(false)
|
||||
localStorage.setItem('pwa-install-prompt-seen', 'true')
|
||||
}
|
||||
|
||||
const handleDismiss = () => {
|
||||
setShowPrompt(false)
|
||||
localStorage.setItem('pwa-install-prompt-seen', 'true')
|
||||
}
|
||||
|
||||
if (isInstalled) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isInstallable) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 浮动安装提示 */}
|
||||
{showPrompt && (
|
||||
<div className="fixed bottom-4 right-4 z-50 max-w-sm animate-in slide-in-from-bottom-5">
|
||||
<Card className="shadow-lg border-2 border-primary">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Smartphone className="h-5 w-5 text-primary" />
|
||||
<CardTitle className="text-base">安装应用</CardTitle>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
onClick={handleDismiss}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<CardDescription className="text-xs">
|
||||
将家谱应用添加到主屏幕,随时随地访问
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<Button
|
||||
onClick={handleInstallClick}
|
||||
className="w-full"
|
||||
size="sm"
|
||||
>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
立即安装
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 设置页面的安装按钮 */}
|
||||
{!showPrompt && (
|
||||
<Alert className="cursor-pointer hover:bg-muted/50 transition-colors" onClick={handleInstallClick}>
|
||||
<Download className="h-4 w-4" />
|
||||
<AlertDescription className="flex items-center justify-between">
|
||||
<span>将应用安装到设备</span>
|
||||
<Button size="sm" variant="outline">
|
||||
安装
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { registerServiceWorker } from "@/lib/register-sw"
|
||||
import { PWAInstaller } from "./pwa-installer"
|
||||
|
||||
export function PWAProvider({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
registerServiceWorker()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<PWAInstaller />
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user