Files
cozsweet-frontend-nextjs/src/app/chat/components/pwa-install-overlay.tsx
T

90 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 + windowSSR 安全)
// 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();
}}
/>
);
}