feat(cms): integrate global layout with CMS and document decision

- Move site-config and navigation to CMS-backed SiteConfigProvider
- Keep static constants as fallback for dev and degraded modes
- Add ADR-0005 recording the global layout CMS strategy

Refs ADR-0005
This commit is contained in:
张翔
2026-07-08 14:46:14 +08:00
parent 38be4a19ef
commit f4b98fb730
6 changed files with 282 additions and 39 deletions
+15 -13
View File
@@ -15,6 +15,8 @@ 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 } from "@/lib/site-config";
import { COMPANY_INFO } from "@/lib/constants/company";
import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from "@/lib/constants/navigation";
export const metadata: Metadata = {
metadataBase: new URL("https://www.novalon.cn"),
@@ -90,19 +92,19 @@ export default async function RootLayout({
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[]>) || {},
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,
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: (navData?.megaDropdown as Record<string, MegaDropdownGroup[]>) || MEGA_DROPDOWN_DATA,
};
return (
+1 -1
View File
@@ -4,7 +4,7 @@ import { useRef, useEffect } from 'react';
import { ChevronDown, Lock, Sparkles } from 'lucide-react';
import { StaticLink } from '@/components/ui/static-link';
import { AnimatePresence, motion } from 'framer-motion';
import type { MegaDropdownGroup } from '@/lib/constants/navigation';
import type { MegaDropdownGroup } from '@/lib/site-config';
interface MegaDropdownProps {
label: string;
+35 -21
View File
@@ -233,17 +233,19 @@ const solutionFields: FieldDefinition[] = [
const heroBannerFields: FieldDefinition[] = [
{
name: 'heading',
name: 'headingTop',
type: 'text',
label: '标题',
label: '顶部小标题',
required: true,
description: '显示在主标题上方的引导语',
ui: { width: 'full' },
},
{
name: 'subheading',
type: 'textarea',
label: '标题',
name: 'headingBottom',
type: 'text',
label: '标题',
required: true,
description: 'Hero 区核心大标题',
ui: { width: 'full' },
},
{
@@ -252,36 +254,48 @@ const heroBannerFields: FieldDefinition[] = [
label: '描述文本',
},
{
name: 'primaryCtaText',
name: 'ctaLabel',
type: 'text',
label: '主按钮文案',
defaultValue: '了解更多',
defaultValue: '预约咨询',
},
{
name: 'primaryCtaLink',
name: 'ctaHref',
type: 'text',
label: '主按钮链接',
defaultValue: '/solutions',
},
{
name: 'secondaryCtaText',
type: 'text',
label: '副按钮文案',
defaultValue: '联系我们',
},
{
name: 'secondaryCtaLink',
type: 'text',
label: '副按钮链接',
defaultValue: '/contact',
},
{
name: 'statValue',
type: 'text',
label: '统计数值',
description: 'Hero 区右侧可选数据指标,如「500+」',
},
{
name: 'statSubtext',
type: 'text',
label: '统计说明',
description: '与统计数值配套的说明文字',
},
{
name: 'ctaTitleLine1',
type: 'text',
label: 'CTA 标题第一行',
description: '底部 CTA 区域第一行标题',
},
{
name: 'ctaTitleLine2',
type: 'text',
label: 'CTA 标题第二行',
description: '底部 CTA 区域第二行标题(通常使用品牌红色)',
},
{
name: 'theme',
type: 'text',
label: '主题风格',
defaultValue: 'brand',
options: [
{ label: '品牌绿', value: 'brand' },
{ label: '品牌', value: 'brand' },
{ label: '蓝', value: 'blue' },
{ label: '青', value: 'teal' },
{ label: '琥珀', value: 'amber' },
+5
View File
@@ -14,6 +14,11 @@ export interface CaseStudyData {
testimonial?: CaseTestimonial;
color: 'brand' | 'blue' | 'teal' | 'amber' | 'purple';
featured?: boolean;
projectDuration?: string;
departments?: string[];
dataScale?: string;
businessProblem?: string;
verified?: boolean;
}
export type FieldType =