feat: 更新 PWA 安装逻辑,添加 Service Worker 注册和安装提示处理

This commit is contained in:
2026-01-06 22:40:51 +07:00
parent db3a2327c8
commit e4e4d50ca8
4 changed files with 115 additions and 77 deletions

View File

@@ -5,28 +5,9 @@ interface BeforeInstallPromptEvent extends Event {
export function usePWAInstall() {
const deferredPrompt = ref<BeforeInstallPromptEvent | null>(null);
const showInstallButton = ref(false);
const canInstall = ref(false);
const isInstalled = ref(false);
// 检查是否已安装
function checkIfInstalled() {
// 检查是否在独立模式下运行(已安装)
if (window.matchMedia("(display-mode: standalone)").matches) {
isInstalled.value = true;
console.log("[PWA] Already installed (standalone mode)");
return true;
}
// 检查 iOS Safari 独立模式
if ((window.navigator as any).standalone === true) {
isInstalled.value = true;
console.log("[PWA] Already installed (iOS standalone)");
return true;
}
return false;
}
// 检测是否是 iOS 设备
function isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream;
@@ -37,64 +18,84 @@ export function usePWAInstall() {
return isIOS() && !(window.navigator as any).standalone;
}
// 在全局作用域设置监听器(不在 onMounted 中)
if (typeof window !== "undefined") {
// 使用 onMounted 确保在客户端环境执行
onMounted(() => {
console.log("[PWA] usePWAInstall mounted");
// 检查是否已安装
if (!checkIfInstalled()) {
// 监听安装提示事件(仅 Android/Chrome
window.addEventListener("beforeinstallprompt", (e: Event) => {
console.log("[PWA] beforeinstallprompt event fired");
e.preventDefault();
deferredPrompt.value = e as BeforeInstallPromptEvent;
showInstallButton.value = true;
});
// 监听安装成功事件
window.addEventListener("appinstalled", () => {
console.log("[PWA] App installed successfully");
deferredPrompt.value = null;
showInstallButton.value = false;
isInstalled.value = true;
});
// iOS 设备也显示安装按钮iOS 不支持 beforeinstallprompt
if (isIOSSafari()) {
console.log("[PWA] iOS Safari detected, showing install button");
showInstallButton.value = true;
}
if (window.matchMedia("(display-mode: standalone)").matches) {
isInstalled.value = true;
console.log("[PWA] Already installed (standalone mode)");
return;
}
}
async function promptInstall() {
// 检查 iOS Safari 独立模式
if ((window.navigator as any).standalone === true) {
isInstalled.value = true;
console.log("[PWA] Already installed (iOS standalone)");
return;
}
// 监听安装提示事件(仅 Android/Chrome
window.addEventListener("beforeinstallprompt", (e: Event) => {
console.log("[PWA] beforeinstallprompt event fired");
e.preventDefault();
deferredPrompt.value = e as BeforeInstallPromptEvent;
canInstall.value = true;
});
// 监听安装成功事件
window.addEventListener("appinstalled", () => {
console.log("[PWA] App installed successfully");
deferredPrompt.value = null;
canInstall.value = false;
isInstalled.value = true;
});
// iOS 设备也显示安装按钮iOS 不支持 beforeinstallprompt
if (isIOSSafari()) {
console.log("[PWA] iOS Safari detected, showing install button");
canInstall.value = true;
}
});
async function install() {
// iOS 设备返回特殊标识,由组件处理
if (isIOSSafari()) {
console.log("[PWA] iOS: Showing manual install guide");
return { outcome: "ios-instruction" as const };
return { outcome: "ios-instruction" as const, success: false };
}
// Android/Chrome 设备
if (!deferredPrompt.value) {
console.log("[PWA] No deferred prompt available");
return { outcome: "not-available" as const };
return { outcome: "not-available" as const, success: false };
}
console.log("[PWA] Showing install prompt");
await deferredPrompt.value.prompt();
const { outcome } = await deferredPrompt.value.userChoice;
console.log("[PWA] User choice:", outcome);
try {
console.log("[PWA] Showing install prompt");
await deferredPrompt.value.prompt();
const { outcome } = await deferredPrompt.value.userChoice;
console.log("[PWA] User choice:", outcome);
if (outcome === "accepted") {
showInstallButton.value = false;
deferredPrompt.value = null;
if (outcome === "accepted") {
canInstall.value = false;
deferredPrompt.value = null;
return { outcome, success: true };
}
return { outcome, success: false };
}
catch (error) {
console.error("[PWA] Install failed:", error);
return { outcome: "error" as const, success: false };
}
return { outcome };
}
return {
showInstallButton: computed(() => showInstallButton.value && !isInstalled.value),
canInstall,
isInstalled,
promptInstall,
install,
isIOS: isIOS(),
isIOSSafari: isIOSSafari(),
};