feat(app): 全局样式重构与核心应用层更新

- 重构 globals.css 设计令牌系统,对齐咨询风品牌色与排版
- 更新根布局,集成 SEO schema 与黑暗模式防闪烁脚本
- 添加 robots.ts 与 sitemap.ts 动态生成
- 添加 middleware.ts 中间件层
- 更新隐私政策与服务条款页面
This commit is contained in:
张翔
2026-07-07 06:52:19 +08:00
parent cf99e7556c
commit 9053f69123
12 changed files with 1444 additions and 975 deletions
+9 -9
View File
@@ -21,10 +21,10 @@ export default function Error({
<div className="container-wide px-4 py-20">
<div className="max-w-2xl mx-auto text-center">
<div className="mb-8">
<div className="w-24 h-24 bg-[var(--color-brand-primary-bg)] rounded-full flex items-center justify-center mx-auto mb-6">
<AlertTriangle className="w-12 h-12 text-[var(--color-brand-primary)]" />
<div className="w-24 h-24 bg-[var(--color-brand-bg)] rounded-full flex items-center justify-center mx-auto mb-6">
<AlertTriangle className="w-12 h-12 text-[var(--color-brand)]" />
</div>
<div className="w-32 h-1 bg-[var(--color-brand-primary)] mx-auto" />
<div className="w-32 h-1 bg-[var(--color-brand)] mx-auto" />
</div>
<h1 className="text-3xl font-bold text-[var(--color-text-primary)] mb-4">
@@ -53,7 +53,7 @@ export default function Error({
<Button
size="lg"
onClick={reset}
className="bg-[var(--color-brand-primary)] hover:bg-[var(--color-brand-primary-hover)] text-white"
className="bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] text-white"
>
<RefreshCw className="w-5 h-5 mr-2" />
@@ -81,8 +81,8 @@ export default function Error({
href="/contact"
className="flex items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center mr-4 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<AlertTriangle className="w-5 h-5 text-[var(--color-brand-primary)]" />
<div className="w-10 h-10 bg-[var(--color-brand-bg)] rounded-lg flex items-center justify-center mr-4 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<AlertTriangle className="w-5 h-5 text-[var(--color-brand)]" />
</div>
<div className="text-left">
<div className="font-semibold text-[var(--color-text-primary)]"></div>
@@ -94,8 +94,8 @@ export default function Error({
href="/services"
className="flex items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center mr-4 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<RefreshCw className="w-5 h-5 text-[var(--color-brand-primary)]" />
<div className="w-10 h-10 bg-[var(--color-brand-bg)] rounded-lg flex items-center justify-center mr-4 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<RefreshCw className="w-5 h-5 text-[var(--color-brand)]" />
</div>
<div className="text-left">
<div className="font-semibold text-[var(--color-text-primary)]"></div>
@@ -107,7 +107,7 @@ export default function Error({
<div className="mt-8 text-sm text-[var(--color-text-placeholder)]">
{' '}
<StaticLink href="/contact" className="text-[var(--color-brand-primary)] hover:underline">
<StaticLink href="/contact" className="text-[var(--color-brand)] hover:underline">
</StaticLink>
</div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

+801 -512
View File
File diff suppressed because it is too large Load Diff
+41 -54
View File
@@ -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>
+14 -9
View File
@@ -1,16 +1,21 @@
'use client';
import { useEffect } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { Home, ArrowLeft, Search } from 'lucide-react';
export default function NotFound() {
useEffect(() => {
document.title = '页面未找到 - 睿新致远';
}, []);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)] flex items-center justify-center">
<div className="container-wide px-4 py-20">
<div className="max-w-xl mx-auto text-center">
<div className="mb-8">
<h1 className="text-8xl sm:text-[120px] font-bold text-[var(--color-brand-primary)] leading-none mb-4 opacity-20">
<h1 className="text-8xl sm:text-[120px] font-bold text-[var(--color-brand)] leading-none mb-4 opacity-20">
404
</h1>
</div>
@@ -54,8 +59,8 @@ export default function NotFound() {
href="/products"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
<div className="w-9 h-9 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand-primary)]" />
<div className="w-9 h-9 bg-[var(--color-brand-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand)]" />
</div>
<span className="font-medium text-sm text-[var(--color-text-primary)]"></span>
</StaticLink>
@@ -64,8 +69,8 @@ export default function NotFound() {
href="/solutions"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
<div className="w-9 h-9 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand-primary)]" />
<div className="w-9 h-9 bg-[var(--color-brand-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand)]" />
</div>
<span className="font-medium text-sm text-[var(--color-text-primary)]"></span>
</StaticLink>
@@ -74,8 +79,8 @@ export default function NotFound() {
href="/about"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
<div className="w-9 h-9 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand-primary)]" />
<div className="w-9 h-9 bg-[var(--color-brand-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand)]" />
</div>
<span className="font-medium text-sm text-[var(--color-text-primary)]"></span>
</StaticLink>
@@ -84,8 +89,8 @@ export default function NotFound() {
href="/contact"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
<div className="w-9 h-9 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand-primary)]" />
<div className="w-9 h-9 bg-[var(--color-brand-bg)] rounded-lg flex items-center justify-center mb-2 group-hover:bg-[var(--color-challenge-isolation-hover)] transition-colors">
<Search className="w-4 h-4 text-[var(--color-brand)]" />
</div>
<span className="font-medium text-sm text-[var(--color-text-primary)]"></span>
</StaticLink>
+16 -11
View File
@@ -8,38 +8,43 @@ describe('PrivacyPolicyPage', () => {
});
describe('Rendering', () => {
it('should render privacy policy page', () => {
render(<PrivacyPolicyPage />);
it('should render privacy policy page', async () => {
const page = await PrivacyPolicyPage();
render(page);
const container = screen.getByText('隐私政策').closest('div');
expect(container).toBeInTheDocument();
});
it('should render page title', () => {
render(<PrivacyPolicyPage />);
it('should render page title', async () => {
const page = await PrivacyPolicyPage();
render(page);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent('隐私政策');
});
it('should render introduction section', () => {
render(<PrivacyPolicyPage />);
it('should render introduction section', async () => {
const page = await PrivacyPolicyPage();
render(page);
const intro = screen.getByText('引言');
expect(intro).toBeInTheDocument();
});
it('should render information collection section', () => {
render(<PrivacyPolicyPage />);
it('should render information collection section', async () => {
const page = await PrivacyPolicyPage();
render(page);
const section = screen.getByText(/我们如何收集和使用您的个人信息/i);
expect(section).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have proper heading hierarchy', () => {
render(<PrivacyPolicyPage />);
it('should have proper heading hierarchy', async () => {
const page = await PrivacyPolicyPage();
render(page);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
const h2s = screen.getAllByRole('heading', { level: 2 });
expect(h2s.length).toBeGreaterThan(0);
});
+238 -212
View File
@@ -1,4 +1,5 @@
import { Metadata } from 'next';
import { getPublishedItems } from '@/lib/cms/data-server';
import { COMPANY_INFO } from '@/lib/constants';
export const metadata: Metadata = {
@@ -6,10 +7,237 @@ export const metadata: Metadata = {
description: `${COMPANY_INFO.name}隐私政策`,
};
export default function PrivacyPolicyPage() {
function PrivacyContent() {
return (
<div className="prose prose-lg max-w-none">
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
访使使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4">使</h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">1.1 </h3>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li>使使访</li>
<li>使</li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">1.2 使</h3>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li>使</li>
<li></li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.1 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li></li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.2 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li></li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.3 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使访使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li>使</li>
<li>访访</li>
<li></li>
<li></li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li>访</li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<p className="text-[var(--color-text-muted)] leading-relaxed mt-4">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed">
18使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4">Cookie </h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.1 Cookie 使</h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使 Cookie Cookie
</p>
<div className="overflow-x-auto mb-6">
<table className="min-w-full border border-[var(--color-border)] rounded-lg">
<thead className="bg-[var(--color-bg-tertiary)]">
<tr>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]">Cookie </th>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]"></th>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]"></th>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]"></th>
</tr>
</thead>
<tbody className="divide-y divide-[var(--color-border)]">
<tr>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"> Cookie</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
</tr>
<tr>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"> Cookie</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">使</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">14</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
</tr>
<tr>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"> Cookie</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">广使</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">-</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
</tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.2 Google Analytics 使</h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使 Google Analytics 4 Google LLC 使访使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-2">
<strong></strong>
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-1 mb-4">
<li>访</li>
<li></li>
<li>/IP </li>
<li>访访</li>
</ul>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-2">
<strong></strong>
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-1 mb-4">
<li>IP </li>
<li> 14 </li>
<li>广</li>
<li> Google </li>
<li> Google 广</li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.3 </h3>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li>访 Cookie</li>
<li>&ldquo;Cookie &rdquo;</li>
<li> Cookie</li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.4 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
<ul className="list-none text-[var(--color-text-muted)] space-y-1 mt-2">
<li>privacy@novalon.cn</li>
<li>驿12</li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-none text-[var(--color-text-muted)] space-y-2">
<li></li>
<li>contact@novalon.cn</li>
<li>privacy@novalon.cn</li>
<li>驿12</li>
</ul>
</section>
</div>
);
}
export default async function PrivacyPolicyPage() {
let cmsContent: string | null = null;
try {
const items = await getPublishedItems('legal-page');
const privacyItem = items.find((item) => item.data.pageType === 'privacy');
if (privacyItem && typeof privacyItem.data.content === 'string' && privacyItem.data.content.length > 0) {
cmsContent = privacyItem.data.content as string;
}
} catch {
// CMS not available, fall back to inline content
}
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<div className="bg-gradient-to-br from-[var(--color-brand-primary)] via-[var(--color-brand-primary)]/80 to-[var(--color-hero-dark-end)] py-20">
<div className="bg-gradient-to-br from-[var(--color-brand)] via-[var(--color-brand)]/80 to-[var(--color-hero-dark-end)] py-20">
<div className="container-wide">
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">
@@ -22,216 +250,14 @@ export default function PrivacyPolicyPage() {
<div className="container-wide py-16">
<div className="max-w-4xl">
<div className="prose prose-lg max-w-none">
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
访使使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4">使</h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">1.1 </h3>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li>使使访</li>
<li>使</li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">1.2 使</h3>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li>使</li>
<li></li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.1 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li></li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.2 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li></li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.3 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使访使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li>使</li>
<li>访访</li>
<li></li>
<li></li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li>访</li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<p className="text-[var(--color-text-muted)] leading-relaxed mt-4">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed">
18使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4">Cookie </h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.1 Cookie 使</h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使 Cookie Cookie
</p>
<div className="overflow-x-auto mb-6">
<table className="min-w-full border border-[var(--color-border)] rounded-lg">
<thead className="bg-[var(--color-bg-tertiary)]">
<tr>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]">Cookie </th>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]"></th>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]"></th>
<th className="px-4 py-3 text-left text-sm font-semibold text-[var(--color-text-primary)] border-b border-[var(--color-border)]"></th>
</tr>
</thead>
<tbody className="divide-y divide-[var(--color-border)]">
<tr>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"> Cookie</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
</tr>
<tr>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"> Cookie</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">使</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">14</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
</tr>
<tr>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"> Cookie</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">广使</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]">-</td>
<td className="px-4 py-3 text-sm text-[var(--color-text-muted)]"></td>
</tr>
</tbody>
</table>
</div>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.2 Google Analytics 使</h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使 Google Analytics 4 Google LLC 使访使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-2">
<strong></strong>
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-1 mb-4">
<li>访</li>
<li></li>
<li>/IP </li>
<li>访访</li>
</ul>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-2">
<strong></strong>
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-1 mb-4">
<li>IP </li>
<li> 14 </li>
<li>广</li>
<li> Google </li>
<li> Google 广</li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.3 </h3>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li>访 Cookie</li>
<li>&ldquo;Cookie &rdquo;</li>
<li> Cookie</li>
</ul>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">7.4 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
<ul className="list-none text-[var(--color-text-muted)] space-y-1 mt-2">
<li>privacy@novalon.cn</li>
<li>驿12</li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-none text-[var(--color-text-muted)] space-y-2">
<li></li>
<li>contact@novalon.cn</li>
<li>privacy@novalon.cn</li>
<li>驿12</li>
</ul>
</section>
</div>
{cmsContent ? (
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: cmsContent }}
/>
) : (
<PrivacyContent />
)}
</div>
</div>
</div>
+27
View File
@@ -0,0 +1,27 @@
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/_next/',
'/api/',
'/admin/',
],
},
{
userAgent: 'Googlebot',
allow: '/',
disallow: [
'/_next/',
'/api/',
],
},
],
sitemap: 'https://www.novalon.cn/sitemap.xml',
host: 'https://www.novalon.cn',
};
}
+76
View File
@@ -0,0 +1,76 @@
import type { MetadataRoute } from 'next';
import { getAllPublishedSlugs } from '@/lib/cms/data-server';
const BASE_URL = 'https://www.novalon.cn';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const staticPages: MetadataRoute.Sitemap = [
{ url: `${BASE_URL}/`, lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
{ url: `${BASE_URL}/about`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 },
{ url: `${BASE_URL}/services`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 },
{ url: `${BASE_URL}/products`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 },
{ url: `${BASE_URL}/solutions`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 },
{ url: `${BASE_URL}/cases`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8 },
{ url: `${BASE_URL}/news`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.7 },
{ url: `${BASE_URL}/team`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.6 },
{ url: `${BASE_URL}/contact`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.7 },
];
try {
// 从 CMS 获取动态页面 slug
const [caseSlugs, newsSlugs, serviceSlugs, productSlugs, solutionSlugs] = await Promise.all([
getAllPublishedSlugs('case-study'),
getAllPublishedSlugs('news'),
getAllPublishedSlugs('service'),
getAllPublishedSlugs('product'),
getAllPublishedSlugs('solution'),
]);
const casePages: MetadataRoute.Sitemap = caseSlugs.map(({ slug }) => ({
url: `${BASE_URL}/cases/${slug}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
}));
const newsPages: MetadataRoute.Sitemap = newsSlugs.map(({ slug }) => ({
url: `${BASE_URL}/news/${slug}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.6,
}));
const servicePages: MetadataRoute.Sitemap = serviceSlugs.map(({ slug }) => ({
url: `${BASE_URL}/services/${slug}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
}));
const productPages: MetadataRoute.Sitemap = productSlugs.map(({ slug }) => ({
url: `${BASE_URL}/products/${slug}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
}));
const solutionPages: MetadataRoute.Sitemap = solutionSlugs.map(({ slug }) => ({
url: `${BASE_URL}/solutions/${slug}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
}));
return [
...staticPages,
...casePages,
...newsPages,
...servicePages,
...productPages,
...solutionPages,
];
} catch {
// 数据库不可用时返回静态页面
return staticPages;
}
}
+16 -11
View File
@@ -8,38 +8,43 @@ describe('TermsOfServicePage', () => {
});
describe('Rendering', () => {
it('should render terms of service page', () => {
render(<TermsOfServicePage />);
it('should render terms of service page', async () => {
const page = await TermsOfServicePage();
render(page);
const container = screen.getByText('服务条款').closest('div');
expect(container).toBeInTheDocument();
});
it('should render page title', () => {
render(<TermsOfServicePage />);
it('should render page title', async () => {
const page = await TermsOfServicePage();
render(page);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent('服务条款');
});
it('should render introduction section', () => {
render(<TermsOfServicePage />);
it('should render introduction section', async () => {
const page = await TermsOfServicePage();
render(page);
const intro = screen.getByText('引言');
expect(intro).toBeInTheDocument();
});
it('should render service content section', () => {
render(<TermsOfServicePage />);
it('should render service content section', async () => {
const page = await TermsOfServicePage();
render(page);
const sections = screen.getAllByRole('heading', { level: 2 });
expect(sections.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have proper heading hierarchy', () => {
render(<TermsOfServicePage />);
it('should have proper heading hierarchy', async () => {
const page = await TermsOfServicePage();
render(page);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
const h2s = screen.getAllByRole('heading', { level: 2 });
expect(h2s.length).toBeGreaterThan(0);
});
+183 -157
View File
@@ -1,4 +1,5 @@
import { Metadata } from 'next';
import { getPublishedItems } from '@/lib/cms/data-server';
import { COMPANY_INFO } from '@/lib/constants';
export const metadata: Metadata = {
@@ -6,10 +7,182 @@ export const metadata: Metadata = {
description: `${COMPANY_INFO.name}服务条款`,
};
export default function TermsOfServicePage() {
function TermsContent() {
return (
<div className="prose prose-lg max-w-none">
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使&ldquo;&rdquo;&ldquo;&rdquo;使使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
ERPCRMBI等
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.1 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.2 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">5.1 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">5.2 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">5.3 退</h3>
<p className="text-[var(--color-text-muted)] leading-relaxed">
退使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li></li>
<li>使使</li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li>使</li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li>使</li>
<li>使</li>
<li>使使</li>
<li>便</li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-none text-[var(--color-text-muted)] space-y-2">
<li></li>
<li>contact@novalon.cn</li>
<li>驿12</li>
</ul>
</section>
<section className="bg-[var(--color-warning-bg)] p-6 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<div className="w-2 h-2 bg-[var(--color-brand)] rounded-full" />
<p className="text-[var(--color-text-primary)] font-medium"></p>
</div>
<p className="text-[var(--color-text-muted)] pl-4">2026226</p>
</section>
</div>
);
}
export default async function TermsOfServicePage() {
let cmsContent: string | null = null;
try {
const items = await getPublishedItems('legal-page');
const termsItem = items.find((item) => item.data.pageType === 'terms');
if (termsItem && typeof termsItem.data.content === 'string' && termsItem.data.content.length > 0) {
cmsContent = termsItem.data.content as string;
}
} catch {
// CMS not available, fall back to inline content
}
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<div className="bg-gradient-to-br from-[var(--color-brand-primary)] via-[var(--color-brand-primary)]/80 to-[var(--color-hero-dark-end)] py-20">
<div className="bg-gradient-to-br from-[var(--color-brand)] via-[var(--color-brand)]/80 to-[var(--color-hero-dark-end)] py-20">
<div className="container-wide">
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">
@@ -22,161 +195,14 @@ export default function TermsOfServicePage() {
<div className="container-wide py-16">
<div className="max-w-4xl">
<div className="prose prose-lg max-w-none">
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使&ldquo;&rdquo;&ldquo;&rdquo;使使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
ERPCRMBI等
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.1 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">2.2 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2 mb-4">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">
使使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">5.1 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">5.2 </h3>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">5.3 退</h3>
<p className="text-[var(--color-text-muted)] leading-relaxed">
退使
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li></li>
<li>使使</li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
使
</p>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li></li>
<li></li>
<li>使</li>
<li></li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<ul className="list-disc pl-6 text-[var(--color-text-muted)] space-y-2">
<li>使</li>
<li>使</li>
<li>使使</li>
<li>便</li>
</ul>
</section>
<section className="mb-12">
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-4"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-4">
</p>
<ul className="list-none text-[var(--color-text-muted)] space-y-2">
<li></li>
<li>contact@novalon.cn</li>
<li>驿12</li>
</ul>
</section>
<section className="bg-[var(--color-warning-bg)] p-6 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<div className="w-2 h-2 bg-[var(--color-brand-primary)] rounded-full" />
<p className="text-[var(--color-text-primary)] font-medium"></p>
</div>
<p className="text-[var(--color-text-muted)] pl-4">2026226</p>
</section>
</div>
{cmsContent ? (
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: cmsContent }}
/>
) : (
<TermsContent />
)}
</div>
</div>
</div>
+23
View File
@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 只处理 /admin 路由(除了登录页和 API 路由)
if (pathname.startsWith('/admin') && !pathname.startsWith('/admin/login') && !pathname.startsWith('/api/')) {
const token = request.cookies.get('novalon_token')?.value;
const authHeader = request.headers.get('authorization');
// 如果 cookie 和 header 都没有 token,重定向到登录页
if (!token && !authHeader) {
// 允许客户端组件处理认证(因为 AuthProvider 在客户端也会检查 localStorage
return NextResponse.next();
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/admin/:path*'],
};