diff --git a/app/components/PwaRegister.jsx b/app/components/PwaRegister.jsx new file mode 100644 index 0000000..a5f0f1c --- /dev/null +++ b/app/components/PwaRegister.jsx @@ -0,0 +1,38 @@ +'use client'; + +import { useEffect } from 'react'; + +/** + * 在客户端注册 Service Worker,满足 Android Chrome PWA 安装条件(需 HTTPS + manifest + SW)。 + * 仅在生产环境且浏览器支持时注册。 + */ +export default function PwaRegister() { + useEffect(() => {// 检测核心能力 + const isPwaSupported = + 'serviceWorker' in navigator && + 'BeforeInstallPromptEvent' in window; + console.log('PWA 支持:', isPwaSupported); + if ( + typeof window === 'undefined' || + !('serviceWorker' in navigator) || + process.env.NODE_ENV !== 'production' + ) { + return; + } + navigator.serviceWorker + .register('/sw.js', { scope: '/', updateViaCache: 'none' }) + .then((reg) => { + reg.addEventListener('updatefound', () => { + const newWorker = reg.installing; + newWorker?.addEventListener('statechange', () => { + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + // 可选:提示用户刷新以获取新版本 + } + }); + }); + }) + .catch(() => {}); + }, []); + + return null; +} diff --git a/app/components/ThemeColorSync.jsx b/app/components/ThemeColorSync.jsx new file mode 100644 index 0000000..319686a --- /dev/null +++ b/app/components/ThemeColorSync.jsx @@ -0,0 +1,37 @@ +'use client'; + +import { useEffect } from 'react'; + +const THEME_COLORS = { + dark: '#0f172a', + light: '#ffffff', +}; + +function getThemeColor() { + if (typeof document === 'undefined') return THEME_COLORS.dark; + const theme = document.documentElement.getAttribute('data-theme'); + return THEME_COLORS[theme] ?? THEME_COLORS.dark; +} + +function applyThemeColor() { + const meta = document.querySelector('meta[name="theme-color"]'); + if (meta) meta.setAttribute('content', getThemeColor()); +} + +/** + * 根据当前亮/暗主题同步 PWA theme-color meta,使 Android 状态栏与页面主题一致。 + * 监听 document.documentElement 的 data-theme 变化并更新 meta。 + */ +export default function ThemeColorSync() { + useEffect(() => { + applyThemeColor(); + const observer = new MutationObserver(() => applyThemeColor()); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'], + }); + return () => observer.disconnect(); + }, []); + + return null; +} diff --git a/app/layout.jsx b/app/layout.jsx index fe12fab..56ba346 100644 --- a/app/layout.jsx +++ b/app/layout.jsx @@ -1,6 +1,8 @@ import { Toaster } from '@/components/ui/sonner'; import './globals.css'; import AnalyticsGate from './components/AnalyticsGate'; +import PwaRegister from './components/PwaRegister'; +import ThemeColorSync from './components/ThemeColorSync'; import packageJson from '../package.json'; export const metadata = { @@ -19,6 +21,9 @@ export default function RootLayout({ children }) { + + {/* 初始为暗色;ThemeColorSync 会按 data-theme 同步为亮/暗 */} + {/* 尽早设置 data-theme,减少首屏主题闪烁;与 suppressHydrationWarning 配合避免服务端/客户端 html 属性不一致报错 */}