lovart3/app/layout.tsx
Your Name cc76d87649 修复favicon未替换问题和优化尺寸调整功能
- 彻底修复favicon未替换问题:改进Metadata配置,添加客户端FaviconUpdater组件
- 添加缓存破坏参数,确保浏览器正确加载新的favicon
- 尺寸调整完成后添加下载按钮
- 保持尺寸编辑状态下左右图片虚线框相同尺寸(714x570px)
- 优化工具栏布局,宽高设置移动到选择比例工具栏后面
2025-12-30 00:15:56 +08:00

172 lines
4.7 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.

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Toaster } from "sonner";
import { ThemeProvider } from "@/components/theme-provider";
import { FaviconUpdater } from "@/components/favicon-updater";
import "./globals.css";
import { SessionProvider } from "next-auth/react";
import { getSiteSettings } from "@/lib/db/queries";
export async function generateMetadata(): Promise<Metadata> {
const settings = await getSiteSettings();
// 处理 favicon URL,添加缓存破坏参数确保更新
const faviconUrl = settings.favicon
? `${settings.favicon}${settings.favicon.includes('?') ? '&' : '?'}v=${Date.now()}`
: "/favicon.ico";
return {
title: {
template: `%s | ${settings.siteName}`,
default: settings.siteName,
},
description: settings.siteName,
icons: {
icon: [
{ url: faviconUrl, sizes: "any" },
{ url: faviconUrl, type: "image/x-icon" },
],
apple: faviconUrl,
shortcut: faviconUrl,
},
};
}
export const viewport = {
maximumScale: 1, // Disable auto-zoom on mobile Safari
};
const geist = Geist({
subsets: ["latin"],
display: "swap",
variable: "--font-geist",
});
const geistMono = Geist_Mono({
subsets: ["latin"],
display: "swap",
variable: "--font-geist-mono",
});
const LIGHT_THEME_COLOR = "hsl(0 0% 100%)";
const DARK_THEME_COLOR = "hsl(240deg 10% 3.92%)";
const THEME_COLOR_SCRIPT = `\
(function() {
var html = document.documentElement;
var meta = document.querySelector('meta[name="theme-color"]');
if (!meta) {
meta = document.createElement('meta');
meta.setAttribute('name', 'theme-color');
document.head.appendChild(meta);
}
function updateThemeColor() {
var isDark = html.classList.contains('dark');
meta.setAttribute('content', isDark ? '${DARK_THEME_COLOR}' : '${LIGHT_THEME_COLOR}');
}
var observer = new MutationObserver(updateThemeColor);
observer.observe(html, { attributes: true, attributeFilter: ['class'] });
updateThemeColor();
})();`;
const CHUNK_ERROR_RECOVERY_SCRIPT = `\
(function() {
// 安全地访问sessionStorage(某些浏览器设置可能禁止访问)
function safeGetSessionStorage(key) {
try {
return sessionStorage.getItem(key);
} catch (e) {
return null;
}
}
function safeSetSessionStorage(key, value) {
try {
sessionStorage.setItem(key, value);
return true;
} catch (e) {
return false;
}
}
function safeRemoveSessionStorage(key) {
try {
sessionStorage.removeItem(key);
return true;
} catch (e) {
return false;
}
}
var hasReloaded = safeGetSessionStorage('chunk-reload-attempted');
window.addEventListener('error', function(event) {
var isChunkError = event.message && (
event.message.includes('Failed to load chunk') ||
event.message.includes('Loading chunk') ||
event.message.includes('/_next/static/chunks/')
);
if (isChunkError && !hasReloaded) {
console.warn('[ChunkError] 检测到chunk加载失败,自动刷新页面...');
safeSetSessionStorage('chunk-reload-attempted', 'true');
setTimeout(function() {
window.location.reload();
}, 1000);
}
}, true);
// 页面加载成功后清除标记
window.addEventListener('load', function() {
setTimeout(function() {
safeRemoveSessionStorage('chunk-reload-attempted');
}, 2000);
});
})();`;
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
className={`${geist.variable} ${geistMono.variable}`}
// `next-themes` injects an extra classname to the body element to avoid
// visual flicker before hydration. Hence the `suppressHydrationWarning`
// prop is necessary to avoid the React hydration mismatch warning.
// https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-app
lang="zh-CN"
suppressHydrationWarning
>
<head>
<script
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required"
dangerouslySetInnerHTML={{
__html: THEME_COLOR_SCRIPT,
}}
/>
<script
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required"
dangerouslySetInnerHTML={{
__html: CHUNK_ERROR_RECOVERY_SCRIPT,
}}
/>
</head>
<body className="antialiased">
<FaviconUpdater />
<ThemeProvider
attribute="class"
defaultTheme="dark"
disableTransitionOnChange
enableSystem
>
<Toaster position="top-center" />
<SessionProvider>{children}</SessionProvider>
</ThemeProvider>
</body>
</html>
);
}