90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
"use client";
|
||
/**
|
||
* PwaInstallOverlay PWA 安装提示触发器
|
||
*
|
||
* 行为:
|
||
* - 检查 PWA 支持(pwaUtil.isSupported)
|
||
* - 检查 PWA 是否已安装(pwaUtil.isInstalled)—— 已安装则不弹
|
||
* - 检查每日是否已显示(使用 AppStorage 持久化)
|
||
* - 延迟 3.5s 后弹出 PwaInstallDialog
|
||
* - 生产环境有效;开发环境 1s 后立即弹出(测试用)
|
||
*
|
||
* 注意:PWA install prompt API(`beforeinstallprompt`)在浏览器差异较大,
|
||
* 本轮仅实现"显示时机"逻辑,具体 install prompt 调用由 Dialog 内部处理。
|
||
*/
|
||
import { useEffect } from "react";
|
||
|
||
import { SpAsyncUtil } from "@/utils/storage";
|
||
import { pwaUtil } from "@/utils/pwa";
|
||
|
||
import { PwaInstallDialog } from "./pwa-install-dialog";
|
||
import styles from "./pwa-install-overlay.module.css";
|
||
|
||
const PWA_DIALOG_KEY = "cozsweet:lastPwaDialogShown";
|
||
|
||
export function PwaInstallOverlay() {
|
||
useEffect(() => {
|
||
let mounted = true;
|
||
|
||
const init = async () => {
|
||
// 0) PWA 支持 / 安装 双重门禁:
|
||
// isSupported() 检查 serviceWorker + window(SSR 安全)
|
||
// isInstalled() 检查 standalone display-mode(避免给已安装用户再弹)
|
||
if (!pwaUtil.isSupported()) return;
|
||
if (pwaUtil.isInstalled()) return;
|
||
|
||
// 检查每日是否已显示
|
||
const lastResult = await SpAsyncUtil.getString(PWA_DIALOG_KEY);
|
||
if (!lastResult.success) return;
|
||
const today = new Date().toISOString().slice(0, 10);
|
||
if (lastResult.data === today) return; // 今天已显示过
|
||
|
||
// 延迟 3.5s 后显示
|
||
setTimeout(() => {
|
||
if (!mounted) return;
|
||
showPwaDialog();
|
||
}, 3500);
|
||
};
|
||
|
||
void init();
|
||
return () => {
|
||
mounted = false;
|
||
};
|
||
}, []);
|
||
|
||
return <div className={styles.hidden} aria-hidden="true" />;
|
||
}
|
||
|
||
function showPwaDialog() {
|
||
if (typeof document === "undefined") return;
|
||
// 记录显示时间
|
||
const today = new Date().toISOString().slice(0, 10);
|
||
void SpAsyncUtil.setString(PWA_DIALOG_KEY, today);
|
||
|
||
// 创建挂载点
|
||
const root = document.createElement("div");
|
||
document.body.appendChild(root);
|
||
// 用 React DOM 渲染(通过 import)
|
||
import("react-dom/client").then(({ createRoot }) => {
|
||
const reactRoot = createRoot(root);
|
||
reactRoot.render(<PwaInstallDialogMount rootEl={root} reactRoot={reactRoot} />);
|
||
});
|
||
}
|
||
|
||
function PwaInstallDialogMount({
|
||
rootEl,
|
||
reactRoot,
|
||
}: {
|
||
rootEl: HTMLElement;
|
||
reactRoot: { unmount: () => void };
|
||
}) {
|
||
return (
|
||
<PwaInstallDialog
|
||
onClose={() => {
|
||
reactRoot.unmount();
|
||
rootEl.remove();
|
||
}}
|
||
/>
|
||
);
|
||
}
|