Merge branch 'dev' into test

This commit is contained in:
2026-06-17 12:00:55 +08:00
4 changed files with 35 additions and 12 deletions
@@ -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 事件 ready3s 超时)
// - 优先使用已缓存的 deferred prompt
// - 如未缓存,再等待 deferred 事件 ready3s 超时)
// - 调浏览器原生 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
// - loginTokenOAuth/email sync 写下的)→ 视为"已登录"跳 /chat
+17 -6
View File
@@ -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 安装(浏览器原生安装提示)。
*
@@ -170,4 +181,4 @@ export class PwaUtil {
}
/** 全局单例(与 `deviceIdentifier` / `SpAsyncUtil` 命名约定一致) */
export const pwaUtil = new PwaUtil();
export const pwaUtil = new PwaUtil();