复评(impeccable critique)全部 Priority Issues 与 Minor Observations 落地: - P0 数字口径:MetricsBasisNote 单一真源 + metrics-basis.test fs 防线,接入 products/solutions/services/home/about;纯渲染端加角注,不动 DB/seed 值 - P1 转化断头路:敬请期待态 + 404 语境出口 + services 空态双出口 - P1 首页并卡:TrustSection 早鸟横幅并入 CasesSection 单张深色共创卡 - P2 CTA 治理:CONSULT_CTA_ALLOWLIST 白名单 + 逐字符扫描器,/contact CTA 全归一 - P3 polish:footer 补 服务/公司列、contact 死三元/空槽/toast、GlobalErrorTracker 幂等日志 - 文档同源:PRODUCT/CONTEXT 动效带统一 180–280ms;DESIGN.md 立 The 10px Floor; font-floor.test 守卫禁止再引入 <10px(保留 10/11px 有意 Bain 微标签) Gates: type-check 0 · jest 1581/132 · lint 0e/126w · contrast 7/7 · headings 10/10
229 lines
9.9 KiB
TypeScript
229 lines
9.9 KiB
TypeScript
import type { Metadata, Viewport } from "next";
|
||
import localFont from "next/font/local";
|
||
import "./globals.css";
|
||
import { Suspense } from "react";
|
||
import { GoogleAnalyticsWrapper } from "@/components/analytics/GoogleAnalyticsWrapper";
|
||
import { GlobalErrorTracker } from "@/components/analytics/GlobalErrorTracker";
|
||
import { CookieConsent } from "@/components/analytics/CookieConsent";
|
||
import { PerformanceTracker } from "@/components/analytics/PerformanceTracker";
|
||
import { OutboundLinkTracker } from "@/components/analytics/OutboundLinkTracker";
|
||
import { ScrollDepthTracker } from "@/components/analytics/ScrollDepthTracker";
|
||
import { OrganizationSchema, WebsiteSchema, LocalBusinessSchema } from "@/components/seo/structured-data";
|
||
import { MobileTabBar } from "@/components/layout/mobile-tab-bar";
|
||
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";
|
||
import { getPublishedItems } from "@/lib/cms/data-server";
|
||
import { SiteConfigProvider, type SiteConfig, type NavigationItem, type MegaDropdownGroup, type MegaDropdownItem } from "@/lib/site-config";
|
||
import { COMPANY_INFO, BRAND_NARRATIVE, type BrandPillar } from "@/lib/constants/company";
|
||
import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from "@/lib/constants/navigation";
|
||
|
||
/**
|
||
* 拉丁字形使用 Geist(本地 woff2,无网络请求);
|
||
* 中文由 globals.css 的 --font-sans 回退到 PingFang SC / 微软雅黑。
|
||
* 这样英文数字有设计字体,中文又不需下载数 MB 的 CJK 字重。
|
||
* 两者总重 140KB,display: swap 不阻塞首屏。
|
||
*/
|
||
const geistSans = localFont({
|
||
src: "./fonts/geist-sans.woff2",
|
||
variable: "--font-geist-sans",
|
||
display: "swap",
|
||
preload: true,
|
||
});
|
||
|
||
const geistMono = localFont({
|
||
src: "./fonts/geist-mono.woff2",
|
||
variable: "--font-geist-mono",
|
||
display: "swap",
|
||
preload: false,
|
||
});
|
||
|
||
export const metadata: Metadata = {
|
||
metadataBase: new URL("https://www.novalon.cn"),
|
||
title: {
|
||
default: "四川睿新致远科技有限公司 - 企业数字化转型服务商",
|
||
template: "%s | 四川睿新致远科技有限公司",
|
||
},
|
||
description: "四川睿新致远科技有限公司成立于2026年1月,专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案。",
|
||
keywords: ["数字化转型", "企业软件", "ERP系统", "CRM系统", "云计算", "数据分析", "软件开发", "成都科技公司", "金融科技", "睿新致远"],
|
||
authors: [{ name: "四川睿新致远科技有限公司" }],
|
||
creator: "四川睿新致远科技有限公司",
|
||
publisher: "四川睿新致远科技有限公司",
|
||
robots: {
|
||
index: true,
|
||
follow: true,
|
||
googleBot: {
|
||
index: true,
|
||
follow: true,
|
||
"max-video-preview": -1,
|
||
"max-image-preview": "large",
|
||
"max-snippet": -1,
|
||
},
|
||
},
|
||
openGraph: {
|
||
type: "website",
|
||
locale: "zh_CN",
|
||
url: "https://www.novalon.cn",
|
||
siteName: "四川睿新致远科技有限公司",
|
||
title: "四川睿新致远科技有限公司 - 企业数字化转型服务商",
|
||
description: "专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案",
|
||
images: [
|
||
{
|
||
url: "/og-image.jpg",
|
||
width: 1200,
|
||
height: 630,
|
||
alt: "四川睿新致远科技有限公司",
|
||
},
|
||
],
|
||
},
|
||
twitter: {
|
||
card: "summary_large_image",
|
||
title: "四川睿新致远科技有限公司 - 企业数字化转型服务商",
|
||
description: "专注于企业数字化转型服务,提供软件开发、云计算、数据分析、信息安全等一站式解决方案",
|
||
images: ["/og-image.jpg"],
|
||
},
|
||
alternates: {
|
||
canonical: "https://www.novalon.cn",
|
||
},
|
||
verification: {
|
||
google: process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION || "",
|
||
},
|
||
};
|
||
|
||
export const viewport: Viewport = {
|
||
width: "device-width",
|
||
initialScale: 1,
|
||
maximumScale: 5,
|
||
themeColor: "#FFFFFF",
|
||
};
|
||
|
||
export default async function RootLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode;
|
||
}>) {
|
||
// Fetch site configuration, navigation, and standalone products from CMS
|
||
const [navItems, configItems, standaloneItems] = await Promise.all([
|
||
getPublishedItems('navigation').catch(() => []),
|
||
getPublishedItems('site-config').catch(() => []),
|
||
getPublishedItems('standalone-product').catch(() => []),
|
||
]);
|
||
|
||
const navData = navItems[0]?.data as Record<string, unknown> | undefined;
|
||
const configData = configItems[0]?.data as Record<string, unknown> | undefined;
|
||
|
||
// Generate standalone product navigation items from CMS data
|
||
const standaloneNavItems: MegaDropdownItem[] = standaloneItems.map((item) => {
|
||
const data = item.data as Record<string, unknown>;
|
||
const externalUrl = data.externalUrl as string | undefined;
|
||
return {
|
||
id: (data.id as string) || item.slug || '',
|
||
title: (data.title as string) || item.title,
|
||
description: (data.description as string) || '',
|
||
href: externalUrl || `/products/standalone/${item.slug || data.id}`,
|
||
badge: (data.status as string) === '内测中' ? '内测中' : undefined,
|
||
};
|
||
});
|
||
|
||
// Build megaDropdown with standalone products dynamically injected
|
||
const baseMegaDropdown = (navData?.megaDropdown as Record<string, MegaDropdownGroup[]>) || MEGA_DROPDOWN_DATA;
|
||
const megaDropdown: Record<string, MegaDropdownGroup[]> = {};
|
||
for (const [key, groups] of Object.entries(baseMegaDropdown)) {
|
||
if (key === 'products' && standaloneNavItems.length > 0) {
|
||
megaDropdown[key] = groups.map((group) => {
|
||
if (group.id === 'standalone-products') {
|
||
return { ...group, description: undefined, items: standaloneNavItems };
|
||
}
|
||
return group;
|
||
});
|
||
} else {
|
||
megaDropdown[key] = groups;
|
||
}
|
||
}
|
||
|
||
const siteConfig: SiteConfig = {
|
||
name: (configData?.name as string) || COMPANY_INFO.name,
|
||
shortName: (configData?.shortName as string) || COMPANY_INFO.shortName,
|
||
displayName: (configData?.displayName as string) || COMPANY_INFO.displayName,
|
||
slogan: (configData?.slogan as string) || COMPANY_INFO.slogan,
|
||
description: (configData?.description as string) || COMPANY_INFO.description,
|
||
valueProposition: (configData?.valueProposition as string) || BRAND_NARRATIVE.valueProposition,
|
||
positioningStatement: (configData?.positioningStatement as string) || BRAND_NARRATIVE.positioningStatement,
|
||
brandPromise: (configData?.brandPromise as string) || BRAND_NARRATIVE.brandPromise,
|
||
toneOfVoice: (configData?.toneOfVoice as string) || BRAND_NARRATIVE.toneOfVoice,
|
||
pillars: (Array.isArray(configData?.pillars) ? configData.pillars : BRAND_NARRATIVE.pillars) as BrandPillar[],
|
||
founded: (configData?.founded as string) || COMPANY_INFO.founded,
|
||
location: (configData?.location as string) || COMPANY_INFO.location,
|
||
email: (configData?.email as string) || COMPANY_INFO.email,
|
||
address: (configData?.address as string) || COMPANY_INFO.address,
|
||
icp: (configData?.icp as string) || COMPANY_INFO.icp,
|
||
police: (configData?.police as string) || COMPANY_INFO.police,
|
||
mainNav: (navData?.mainNav as NavigationItem[]) || NAVIGATION_V2,
|
||
megaDropdown,
|
||
};
|
||
|
||
return (
|
||
<html
|
||
lang="zh-CN"
|
||
className={`scroll-smooth ${geistSans.variable} ${geistMono.variable}`}
|
||
data-scroll-behavior="smooth"
|
||
suppressHydrationWarning
|
||
>
|
||
<head>
|
||
{/* 主题防 FOUC:首帧前同步解析偏好并写入 data-theme,避免刷新闪白/闪黑。
|
||
三档语义(与 src/lib/theme.ts 严格一致,改动时两处必须同步):
|
||
'light' | 'dark' → 用户显式选择,直接生效
|
||
'system' | 无值 → 跟随操作系统 prefers-color-scheme
|
||
显式选择优先于系统偏好;localStorage 不可用(隐私模式)时兜底浅色。 */}
|
||
<script
|
||
dangerouslySetInnerHTML={{
|
||
__html: `(function(){try{var p=localStorage.getItem('novalon-theme');if(p!=='light'&&p!=='dark'&&p!=='system'){p='system';}var t=p;if(p==='system'){t=(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}document.documentElement.setAttribute('data-theme',t);}catch(e){try{document.documentElement.setAttribute('data-theme','light');}catch(e2){}}})();`,
|
||
}}
|
||
/>
|
||
<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" />
|
||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||
<link rel="manifest" href="/site.webmanifest" />
|
||
<meta name="theme-color" content="#C41E3A" />
|
||
<link rel="dns-prefetch" href="//formsubmit.co" />
|
||
<link rel="preconnect" href="//formsubmit.co" crossOrigin="anonymous" />
|
||
<OrganizationSchema />
|
||
<WebsiteSchema />
|
||
<LocalBusinessSchema />
|
||
</head>
|
||
<body
|
||
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 />
|
||
<ErrorBoundary>
|
||
<SiteConfigProvider config={siteConfig}>
|
||
<ClientLayout>
|
||
{children}
|
||
</ClientLayout>
|
||
</SiteConfigProvider>
|
||
</ErrorBoundary>
|
||
<Suspense fallback={null}>
|
||
<MobileTabBar />
|
||
</Suspense>
|
||
<CookieConsent />
|
||
<BackToTop />
|
||
</body>
|
||
</html>
|
||
);
|
||
}
|