Merge branch 'dev' into test
This commit is contained in:
@@ -13,13 +13,13 @@ import styles from "./pwa-install-dialog.module.css";
|
||||
|
||||
export interface PwaInstallDialogProps {
|
||||
onClose: () => void;
|
||||
onInstall?: () => void;
|
||||
onInstall?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function PwaInstallDialog({ onClose, onInstall }: PwaInstallDialogProps) {
|
||||
const handleInstall = () => {
|
||||
const handleInstall = async () => {
|
||||
if (onInstall) {
|
||||
onInstall();
|
||||
await onInstall();
|
||||
} else {
|
||||
// 提示用户使用浏览器"添加到主屏幕"功能
|
||||
window.alert(
|
||||
|
||||
@@ -32,6 +32,8 @@ export function PwaInstallOverlay() {
|
||||
// isInstalled() 检查 standalone display-mode(避免给已安装用户再弹)
|
||||
if (!pwaUtil.isSupported()) return;
|
||||
if (pwaUtil.isInstalled()) return;
|
||||
// 提前挂 beforeinstallprompt 监听,避免用户点 OK 时事件已在更早时机丢失。
|
||||
pwaUtil.prepareInstallPrompt();
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
@@ -92,13 +94,13 @@ function PwaInstallDialogMount({
|
||||
}}
|
||||
onInstall={async () => {
|
||||
// 走 pwaUtil.install():
|
||||
// - 挂 beforeinstallprompt 监听(如未挂)
|
||||
// - 等待 deferred 事件 ready(3s 超时)
|
||||
// - 优先使用已缓存的 deferred prompt
|
||||
// - 如未缓存,再等待 deferred 事件 ready(3s 超时)
|
||||
// - 调浏览器原生 prompt
|
||||
// - 返 "accepted" / "dismissed" / "unavailable"
|
||||
// 当前不消费返回值 —— 用户已经看到 dialog 点了 OK,是否真装上是浏览器的事。
|
||||
// 后续可在此接 metrics 上报(pwa_accepted / pwa_dismissed / pwa_unavailable)。
|
||||
void pwaUtil.install();
|
||||
await pwaUtil.install();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useUserDispatch } from "@/stores/user/user-context";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { pwaUtil } from "@/utils/pwa";
|
||||
|
||||
import { SplashBackground } from "./splash-background";
|
||||
import { SplashLogo } from "./splash-logo";
|
||||
@@ -15,6 +16,11 @@ import { SplashContent } from "./splash-content";
|
||||
import { SplashButton } from "./splash-button";
|
||||
import styles from "./splash-screen.module.css";
|
||||
|
||||
// 尽量早地缓存 beforeinstallprompt,避免用户停留 splash 时浏览器事件已触发并丢失。
|
||||
if (typeof window !== "undefined") {
|
||||
pwaUtil.prepareInstallPrompt();
|
||||
}
|
||||
|
||||
export function SplashScreen() {
|
||||
const router = useRouter();
|
||||
const state = useAuthState();
|
||||
@@ -26,6 +32,10 @@ export function SplashScreen() {
|
||||
router.replace(ROUTES.chat);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
pwaUtil.prepareInstallPrompt();
|
||||
}, []);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// useEffect ① 启动时检查 localStorage 里的 loginToken:
|
||||
// - loginToken(OAuth/email sync 写下的)→ 视为"已登录"跳 /chat
|
||||
|
||||
+16
-5
@@ -2,11 +2,6 @@
|
||||
/**
|
||||
* PwaUtil — PWA 工具类
|
||||
*
|
||||
* 三个核心能力(与 [src/hooks/use-pwa-install.ts] 对齐,但面向非 React 代码):
|
||||
* - `isSupported()` 浏览器是否支持 PWA 安装(serviceWorker + manifest)
|
||||
* - `isInstalled()` PWA 是否已安装为 standalone(避免重复提示)
|
||||
* - `install()` 触发浏览器原生安装流程(`beforeinstallprompt`)
|
||||
*
|
||||
* 设计要点:
|
||||
* - SSR 安全:所有方法 `typeof window === "undefined"` 守卫,SSR 全部返 false / "unavailable"
|
||||
* - 内部缓存 `beforeinstallprompt` 事件对象;install() 时如有缓存直接 prompt,否则等
|
||||
@@ -80,6 +75,22 @@ export class PwaUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提前挂上 `beforeinstallprompt` 监听。
|
||||
*
|
||||
* 目的:浏览器事件常常会在页面稳定后自行触发;如果等到用户点"安装"时才挂监听,
|
||||
* 很容易错过那次事件,导致后续 `install()` 拿不到 deferred prompt。
|
||||
*/
|
||||
prepareInstallPrompt(): void {
|
||||
if (!this.isSupported()) return;
|
||||
this.attachListener();
|
||||
}
|
||||
|
||||
/** 当前是否已缓存可用的原生安装提示事件。 */
|
||||
canPromptInstall(): boolean {
|
||||
return this.deferred !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发 PWA 安装(浏览器原生安装提示)。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user