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