feat(app): 全局样式重构与核心应用层更新
- 重构 globals.css 设计令牌系统,对齐咨询风品牌色与排版 - 更新根布局,集成 SEO schema 与黑暗模式防闪烁脚本 - 添加 robots.ts 与 sitemap.ts 动态生成 - 添加 middleware.ts 中间件层 - 更新隐私政策与服务条款页面
This commit is contained in:
+41
-54
@@ -1,9 +1,6 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import { Ma_Shan_Zheng, Noto_Sans_SC } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Suspense } from "react";
|
||||
import { ThemeProvider } from "@/contexts/theme-context";
|
||||
import { GoogleAnalyticsWrapper } from "@/components/analytics/GoogleAnalyticsWrapper";
|
||||
import { GlobalErrorTracker } from "@/components/analytics/GlobalErrorTracker";
|
||||
import { CookieConsent } from "@/components/analytics/CookieConsent";
|
||||
@@ -16,42 +13,8 @@ import { ErrorBoundary } from "@/components/ui/error-boundary";
|
||||
import { ScrollProgress } from "@/components/ui/scroll-progress";
|
||||
import { BackToTop } from "@/components/ui/back-to-top";
|
||||
import { ClientLayout } from "@/components/layout/client-layout";
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/geist-sans.woff2",
|
||||
variable: "--font-geist-sans",
|
||||
display: "swap",
|
||||
preload: false,
|
||||
});
|
||||
|
||||
const geistMono = localFont({
|
||||
src: "./fonts/geist-mono.woff2",
|
||||
variable: "--font-geist-mono",
|
||||
display: "swap",
|
||||
preload: false,
|
||||
});
|
||||
|
||||
const aoyagiReisho = localFont({
|
||||
src: "./fonts/AoyagiReisho-subset.ttf",
|
||||
variable: "--font-aoyagi-reisho",
|
||||
display: "swap",
|
||||
preload: true,
|
||||
});
|
||||
|
||||
const maShanZheng = Ma_Shan_Zheng({
|
||||
weight: "400",
|
||||
variable: "--font-ma-shan-zheng",
|
||||
display: "swap",
|
||||
preload: false,
|
||||
});
|
||||
|
||||
const notoSansSC = Noto_Sans_SC({
|
||||
weight: ["400", "500", "700"],
|
||||
variable: "--font-noto-sans-sc",
|
||||
display: "swap",
|
||||
subsets: ["latin"],
|
||||
preload: false,
|
||||
});
|
||||
import { getPublishedItems } from "@/lib/cms/data-server";
|
||||
import { SiteConfigProvider, type SiteConfig, type NavigationItem, type MegaDropdownGroup } from "@/lib/site-config";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://www.novalon.cn"),
|
||||
@@ -109,25 +72,42 @@ export const viewport: Viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 5,
|
||||
themeColor: [
|
||||
{ media: "(prefers-color-scheme: light)", color: "#FFFFFF" },
|
||||
{ media: "(prefers-color-scheme: dark)", color: "#0A0A0A" },
|
||||
],
|
||||
themeColor: "#FFFFFF",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
// Fetch site configuration and navigation from CMS
|
||||
const [navItems, configItems] = await Promise.all([
|
||||
getPublishedItems('navigation').catch(() => []),
|
||||
getPublishedItems('site-config').catch(() => []),
|
||||
]);
|
||||
|
||||
const navData = navItems[0]?.data as Record<string, unknown> | undefined;
|
||||
const configData = configItems[0]?.data as Record<string, unknown> | undefined;
|
||||
|
||||
const siteConfig: SiteConfig = {
|
||||
name: (configData?.name as string) || '',
|
||||
shortName: (configData?.shortName as string) || '',
|
||||
displayName: (configData?.displayName as string) || '',
|
||||
slogan: (configData?.slogan as string) || '',
|
||||
description: (configData?.description as string) || '',
|
||||
founded: (configData?.founded as string) || '',
|
||||
location: (configData?.location as string) || '',
|
||||
email: (configData?.email as string) || '',
|
||||
address: (configData?.address as string) || '',
|
||||
icp: (configData?.icp as string) || '',
|
||||
police: (configData?.police as string) || '',
|
||||
mainNav: (navData?.mainNav as NavigationItem[]) || [],
|
||||
megaDropdown: (navData?.megaDropdown as Record<string, MegaDropdownGroup[]>) || {},
|
||||
};
|
||||
|
||||
return (
|
||||
<html lang="zh-CN" className="scroll-smooth" suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(function(){try{var t=localStorage.getItem('novalon-theme');if(t==='dark'||(t==='system'||!t)&&window.matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}}catch(e){}})()`,
|
||||
}}
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
@@ -142,21 +122,28 @@ export default function RootLayout({
|
||||
<LocalBusinessSchema />
|
||||
</head>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} ${aoyagiReisho.variable} ${maShanZheng.variable} ${notoSansSC.variable} font-sans antialiased`}
|
||||
className="font-sans antialiased"
|
||||
>
|
||||
<a
|
||||
href="#main-content"
|
||||
data-skip-to-content="true"
|
||||
className="absolute left-[-9999px] top-0 z-50 px-4 py-2 bg-brand text-white rounded-br-lg shadow-lg focus:left-0 focus:outline-none"
|
||||
>
|
||||
跳转到主要内容
|
||||
</a>
|
||||
<ScrollProgress />
|
||||
<GoogleAnalyticsWrapper />
|
||||
<GlobalErrorTracker />
|
||||
<PerformanceTracker />
|
||||
<OutboundLinkTracker />
|
||||
<ScrollDepthTracker />
|
||||
<ThemeProvider>
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<SiteConfigProvider config={siteConfig}>
|
||||
<ClientLayout>
|
||||
{children}
|
||||
</ClientLayout>
|
||||
</ErrorBoundary>
|
||||
</ThemeProvider>
|
||||
</SiteConfigProvider>
|
||||
</ErrorBoundary>
|
||||
<Suspense fallback={null}>
|
||||
<MobileTabBar />
|
||||
</Suspense>
|
||||
|
||||
Reference in New Issue
Block a user