From 1ae918e391f02155392810022d3e5a18e9745633 Mon Sep 17 00:00:00 2001 From: zhangxiang Date: Mon, 31 Aug 2026 16:17:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(home):=20=E9=A6=96=E9=A1=B5=E5=AF=B9?= =?UTF-8?q?=E6=A0=87=E5=9F=83=E6=A3=AE=E5=93=B2=E9=87=8D=E6=9E=84=20V15?= =?UTF-8?q?=EF=BC=88=E6=B7=B1=E8=89=B2=20Hero=20+=20Insights/FounderQuote/?= =?UTF-8?q?News=20=E6=94=AF=E6=9F=B1=20+=20=E6=A1=88=E4=BE=8B=20Client=20S?= =?UTF-8?q?potlight=EF=BC=89=E5=88=A0=E9=99=A4=20v14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(marketing)/home-content-v14.test.tsx | 138 ----- src/app/(marketing)/home-content-v15.test.tsx | 256 +++++++++ ...e-content-v14.tsx => home-content-v15.tsx} | 505 +++++++++++++++--- src/app/(marketing)/page.test.tsx | 9 +- src/app/(marketing)/page.tsx | 11 +- 5 files changed, 707 insertions(+), 212 deletions(-) delete mode 100644 src/app/(marketing)/home-content-v14.test.tsx create mode 100644 src/app/(marketing)/home-content-v15.test.tsx rename src/app/(marketing)/{home-content-v14.tsx => home-content-v15.tsx} (54%) diff --git a/src/app/(marketing)/home-content-v14.test.tsx b/src/app/(marketing)/home-content-v14.test.tsx deleted file mode 100644 index cad4c49..0000000 --- a/src/app/(marketing)/home-content-v14.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { describe, it, expect, jest } from '@jest/globals'; -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import HomeContentV14 from './home-content-v14'; - -jest.mock('framer-motion', () => ({ - motion: { - div: ({ children, initial, animate, className, style, ...props }: any) => ( -
- {children} -
- ), - h1: ({ children, className, ...props }: any) => ( -

{children}

- ), - p: ({ children, className, ...props }: any) => ( -

{children}

- ), - span: ({ children, className, ...props }: any) => ( - {children} - ), - a: ({ children, href, className, ...props }: any) => ( - {children} - ), - }, - useInView: jest.fn(() => true), - AnimatePresence: ({ children }: any) => <>{children}, -})); - -jest.mock('next/image', () => ({ - __esModule: true, - default: ({ src, alt, className }: any) => ( - // eslint-disable-next-line @next/next/no-img-element - {alt} - ), -})); - -jest.mock('@/components/ui/scroll-reveal', () => ({ - ScrollReveal: ({ children, className }: any) => ( -
{children}
- ), - StaggerReveal: ({ children, className }: any) => ( -
{children}
- ), -})); - -jest.mock('@/components/ui/badge', () => ({ - Badge: ({ children, className }: any) => ( - {children} - ), -})); - -jest.mock('lucide-react', () => { - const mockIcon = (name: string) => { - const Icon = (props: any) => ; - Icon.displayName = name; - return Icon; - }; - return { - ArrowRight: mockIcon('arrow-right'), - ArrowUpRight: mockIcon('arrow-up-right'), - ArrowDownRight: mockIcon('arrow-down-right'), - Lightbulb: mockIcon('lightbulb'), - Database: mockIcon('database'), - Layers: mockIcon('layers'), - Code: mockIcon('code'), - Puzzle: mockIcon('puzzle'), - Server: mockIcon('server'), - LayoutDashboard: mockIcon('layout-dashboard'), - BarChart3: mockIcon('bar-chart3'), - Package: mockIcon('package'), - Users: mockIcon('users'), - Settings: mockIcon('settings'), - Search: mockIcon('search'), - Bell: mockIcon('bell'), - TrendingUp: mockIcon('trending-up'), - AlertCircle: mockIcon('alert-circle'), - }; -}); - -describe('HomeContentV14 - 首页 Hero 优化', () => { - it('renders an abstract data-driven visual without product UI shell', () => { - render(); - - expect(screen.getByTestId('hero-product-visual')).toBeInTheDocument(); - expect(screen.getByText('数据驱动 · 让转型可量化')).toBeInTheDocument(); - }); - - it('keeps a single primary CTA and demotes the secondary action to a text link', () => { - render(); - - expect(screen.getByTestId('hero-primary-cta')).toBeInTheDocument(); - expect(screen.getByTestId('hero-primary-cta')).toHaveAttribute('href', '/contact'); - expect(screen.getByTestId('hero-secondary-link')).toBeInTheDocument(); - }); - - it('keeps the answer-first stats bar visible', () => { - render(); - - expect(screen.getByTestId('hero-stats')).toBeInTheDocument(); - }); -}); - -describe('HomeContentV14 - 信任层与章节式叙事', () => { - it('renders verifiable trust signals with a source annotation', () => { - render(); - - expect(screen.getByTestId('trust-section')).toBeInTheDocument(); - expect(screen.getByText('100%')).toBeInTheDocument(); - expect(screen.getByText('成都')).toBeInTheDocument(); - // 可验证来源标注存在(每个数据卡一个) - expect(screen.getAllByText(/数据来源:/i).length).toBeGreaterThanOrEqual(4); - }); - - it('renders an early-access co-creation label instead of fabricated testimonials', () => { - render(); - - expect(screen.getByTestId('early-access-banner')).toBeInTheDocument(); - expect(screen.getAllByText('首批客户共创中').length).toBeGreaterThanOrEqual(1); - }); - - it('renders the problem-method-result narrative with chapter numbers', () => { - render(); - - expect(screen.getByTestId('narrative-section')).toBeInTheDocument(); - expect(screen.getByText('01')).toBeInTheDocument(); - expect(screen.getByText('02')).toBeInTheDocument(); - expect(screen.getByText('03')).toBeInTheDocument(); - expect(screen.getAllByText(/从问题到结果/).length).toBeGreaterThan(0); - }); -}); diff --git a/src/app/(marketing)/home-content-v15.test.tsx b/src/app/(marketing)/home-content-v15.test.tsx new file mode 100644 index 0000000..d2b4013 --- /dev/null +++ b/src/app/(marketing)/home-content-v15.test.tsx @@ -0,0 +1,256 @@ +import { describe, it, expect, jest } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import HomeContentV15 from './home-content-v15'; + +jest.mock('framer-motion', () => ({ + motion: { + div: ({ children, initial, animate, className, style, ...props }: any) => ( +
+ {children} +
+ ), + h1: ({ children, className, ...props }: any) => ( +

{children}

+ ), + p: ({ children, className, ...props }: any) => ( +

{children}

+ ), + span: ({ children, className, ...props }: any) => ( + {children} + ), + a: ({ children, href, className, ...props }: any) => ( + {children} + ), + }, + useInView: jest.fn(() => true), + AnimatePresence: ({ children }: any) => <>{children}, +})); + +jest.mock('next/image', () => ({ + __esModule: true, + default: ({ src, alt, className }: any) => ( + // eslint-disable-next-line @next/next/no-img-element + {alt} + ), +})); + +jest.mock('@/components/ui/scroll-reveal', () => ({ + ScrollReveal: ({ children, className }: any) => ( +
{children}
+ ), + StaggerReveal: ({ children, className }: any) => ( +
{children}
+ ), +})); + +jest.mock('@/components/ui/badge', () => ({ + Badge: ({ children, className }: any) => ( + {children} + ), +})); + +jest.mock('lucide-react', () => { + const mockIcon = (name: string) => { + const Icon = (props: any) => ; + Icon.displayName = name; + return Icon; + }; + return { + ArrowRight: mockIcon('arrow-right'), + ArrowUpRight: mockIcon('arrow-up-right'), + ArrowDownRight: mockIcon('arrow-down-right'), + Lightbulb: mockIcon('lightbulb'), + Database: mockIcon('database'), + Layers: mockIcon('layers'), + Code: mockIcon('code'), + Puzzle: mockIcon('puzzle'), + Server: mockIcon('server'), + Quote: mockIcon('quote'), + Newspaper: mockIcon('newspaper'), + Calendar: mockIcon('calendar'), + LayoutDashboard: mockIcon('layout-dashboard'), + BarChart3: mockIcon('bar-chart3'), + Package: mockIcon('package'), + Users: mockIcon('users'), + Settings: mockIcon('settings'), + Search: mockIcon('search'), + Bell: mockIcon('bell'), + TrendingUp: mockIcon('trending-up'), + AlertCircle: mockIcon('alert-circle'), + }; +}); + +describe('HomeContentV15 - 深色 Hero(对标 Accenture 纯黑 Hero)', () => { + it('renders the dark canvas hero with a white logo variant', () => { + render(); + + expect(screen.getByTestId('hero-section')).toBeInTheDocument(); + const heroLogo = screen.getByTestId('mock-next-image'); + expect(heroLogo).toHaveAttribute('src', '/logo-white.svg'); + }); + + it('keeps a single primary CTA and demotes the secondary action to a text link', () => { + render(); + + expect(screen.getByTestId('hero-primary-cta')).toBeInTheDocument(); + expect(screen.getByTestId('hero-primary-cta')).toHaveAttribute('href', '/contact'); + expect(screen.getByTestId('hero-secondary-link')).toBeInTheDocument(); + }); + + it('keeps the answer-first stats bar visible', () => { + render(); + + expect(screen.getByTestId('hero-stats')).toBeInTheDocument(); + }); + + it('renders the abstract data-driven visual without product UI shell', () => { + render(); + + expect(screen.getByTestId('hero-product-visual')).toBeInTheDocument(); + expect(screen.getByText('数据驱动 · 让转型可量化')).toBeInTheDocument(); + }); +}); + +describe('HomeContentV15 - 信任层与章节式叙事', () => { + it('renders verifiable trust signals with a source annotation', () => { + render(); + + expect(screen.getByTestId('trust-section')).toBeInTheDocument(); + expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByText('成都')).toBeInTheDocument(); + // 可验证来源标注存在(每个数据卡一个) + expect(screen.getAllByText(/数据来源:/i).length).toBeGreaterThanOrEqual(4); + }); + + it('renders an early-access co-creation label instead of fabricated testimonials', () => { + render(); + + expect(screen.getByTestId('early-access-banner')).toBeInTheDocument(); + expect(screen.getAllByText('首批客户共创中').length).toBeGreaterThanOrEqual(1); + }); + + it('renders the problem-method-result narrative with chapter numbers', () => { + render(); + + expect(screen.getByTestId('narrative-section')).toBeInTheDocument(); + expect(screen.getAllByText('01').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('02').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('03').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText(/从问题到结果/).length).toBeGreaterThan(0); + }); +}); + +describe('HomeContentV15 - 行业洞察区(对标 Accenture Insights)', () => { + it('renders the insights pillar with typed tags and verifiable methodology content', () => { + render(); + + expect(screen.getByTestId('insights-section')).toBeInTheDocument(); + // 类型标签(对标 Research Report / Perspective / Case Study) + expect(screen.getAllByText('方法论').length).toBeGreaterThanOrEqual(2); + expect(screen.getByText('观点')).toBeInTheDocument(); + // 全部链接真实页面,不虚构研究报告 + expect(screen.getByText('数字化转型诊断:从哪里开始,如何衡量')).toBeInTheDocument(); + }); +}); + +describe('HomeContentV15 - 创始人观点区(对标 Accenture CEO Quote)', () => { + it('renders the founder quote with attribution (no fabricated persona)', () => { + render(); + + expect(screen.getByTestId('founder-quote-section')).toBeInTheDocument(); + expect(screen.getByText(/数字化不是一次性项目/)).toBeInTheDocument(); + expect(screen.getByText('Novalon 创始团队')).toBeInTheDocument(); + }); +}); + +describe('HomeContentV15 - 新闻动态区(对标 Accenture News)', () => { + it('renders the news section with latest items and a view-all entry', () => { + render(); + + expect(screen.getByTestId('news-section')).toBeInTheDocument(); + expect(screen.getByText('四川睿新致远科技有限公司正式成立')).toBeInTheDocument(); + expect(screen.getByText('查看全部新闻')).toBeInTheDocument(); + }); + + it('renders CMS-provided news when available', () => { + render(); + + expect(screen.getByText('CMS 新闻条目')).toBeInTheDocument(); + }); +}); + +describe('HomeContentV15 - 案例区 Client Spotlight(对标 Accenture Client Spotlight)', () => { + it('renders a co-creation spotlight card (verifiable, no fabricated clients) when no cases exist', () => { + render(); + + // 空态 spotlight:共创流程 3 步 + 深色卡 CTA + expect(screen.getAllByText('首批客户共创中').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('业务诊断').length).toBeGreaterThanOrEqual(1); + expect(screen.getByText('方案共创')).toBeInTheDocument(); + expect(screen.getByText('验证发布')).toBeInTheDocument(); + expect(screen.getAllByText('01').length).toBeGreaterThanOrEqual(1); + expect(screen.getByTestId('cases-co-creation-cta')).toHaveAttribute('href', '/contact'); + }); + + it('renders a dark spotlight card with metrics for the featured case when data exists', () => { + const mockCases = [ + { + slug: 'spotlight-demo', + industry: '制造业', + title: 'Spotlight 案例标题', + href: '/cases/spotlight-demo', + verified: true, + testimonial: { + quote: '这是一段客户引言,用于验证 spotlight 渲染。', + author: '客户 CIO', + role: '某制造企业', + }, + metrics: [ + { value: '38%', label: '交付效率提升' }, + { value: '2x', label: '数据查询提速' }, + { value: '3周', label: '上线周期' }, + ], + }, + { + slug: 'secondary-case', + industry: '贸易零售', + title: '次要案例标题', + href: '/cases/secondary-case', + metrics: [ + { value: '15%', label: '库存周转改善' }, + { value: '4h', label: '对账耗时' }, + { value: '9成', label: '预测准确率' }, + ], + }, + ]; + + render(); + + // spotlight 大卡:深色画布 + 引言 + 授权 badge + 量化指标 + expect(screen.getByText('Spotlight 案例标题')).toBeInTheDocument(); + expect(screen.getByText('这是一段客户引言,用于验证 spotlight 渲染。')).toBeInTheDocument(); + expect(screen.getByText('已获客户授权')).toBeInTheDocument(); + expect(screen.getByText('38%')).toBeInTheDocument(); + expect(screen.getByText('查看完整案例')).toBeInTheDocument(); + + // 次要案例保持轻量卡片 + expect(screen.getByText('次要案例标题')).toBeInTheDocument(); + expect(screen.getAllByText('了解详情').length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/src/app/(marketing)/home-content-v14.tsx b/src/app/(marketing)/home-content-v15.tsx similarity index 54% rename from src/app/(marketing)/home-content-v14.tsx rename to src/app/(marketing)/home-content-v15.tsx index 9852b81..05a947f 100644 --- a/src/app/(marketing)/home-content-v14.tsx +++ b/src/app/(marketing)/home-content-v15.tsx @@ -7,11 +7,13 @@ import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal'; import { Badge } from '@/components/ui/badge'; import { StaticLink } from '@/components/ui/static-link'; import { HeroProductVisual } from '@/components/sections/hero-product-visual'; -import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server } from 'lucide-react'; +import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server, Quote, Calendar } from 'lucide-react'; import { cn } from '@/lib/utils'; import { EASE_OUT } from '@/components/ui/page-decoration'; import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants'; import { useSiteConfig } from '@/lib/site-config'; +import { type NewsItem } from '@/lib/constants/news'; +import { NEWS } from '@/lib/constants/news'; // ===== 服务图标映射:将字符串键名映射为 Lucide 组件 ===== const SERVICE_ICON_MAP: Record = { @@ -73,6 +75,47 @@ const FALLBACK_SERVICES = [ }, ]; +// ===== 行业洞察兜底数据(对标 Accenture Insights:类型标签 + 卡片)===== +// 零编造原则:全部为可验证的方法论/观点类内容,链接真实页面;不做虚构研究报告 +const FALLBACK_INSIGHTS = [ + { + tag: '方法论', + title: '数字化转型诊断:从哪里开始,如何衡量', + desc: '从业务目标出发的四步诊断法——梳理断点、量化差距、排定优先级、定义验收指标。', + date: '2026-08', + href: '/methodology', + }, + { + tag: '观点', + title: '私有化部署:为什么核心数据需要留在企业内', + desc: '合规要求之外,数据主权正在成为企业数字化决策的第一优先级。', + date: '2026-08', + href: '/services/data', + }, + { + tag: '方法论', + title: '从需求到交付:全流程陪伴式实施如何降低转型风险', + desc: '诊断 → 设计 → 交付 → 运维,每一阶段都有明确的交付物与衡量指标。', + date: '2026-07', + href: '/services/software', + }, + { + tag: '共创', + title: '首批客户共创计划:与早期客户共同打磨产品', + desc: '以真实运营数据验证业务改善,成果在获得客户授权后公开。', + date: '2026-06', + href: '/contact', + }, +]; + +// ===== 创始人观点兜底(对标 Accenture CEO Quote,人格化背书)===== +// 零编造原则:不虚构具体人名/原话;姓名职务为空时仅展示观点立场,真实引言可在 CMS page-copy 配置 +const FALLBACK_FOUNDER_QUOTE = { + text: '我们相信,数字化不是一次性项目,而是持续迭代的能力建设。每一份投入,都应当经得起衡量。', + name: 'Novalon 创始团队', + role: '技术咨询 · 数字化转型同行者', +}; + // ===== 首页结构性文案类型(page-copy CMS 覆盖,未配置时走硬编码兜底)===== type HomePageCopy = Record; @@ -90,7 +133,7 @@ function StyledTitle({ lines }: { lines: (string | undefined)[] }) { ); } -// ===== 品牌 CTA 按钮 ===== +// ===== 品牌 CTA 按钮(Pill 胶囊化,对标 Accenture > CTA)===== function CTAButton({ children, href, variant = 'primary', className, dataTestId }: { children: React.ReactNode; href: string; @@ -103,7 +146,7 @@ function CTAButton({ children, href, variant = 'primary', className, dataTestId href={href} data-testid={dataTestId} className={cn( - 'group inline-flex items-center gap-3 px-10 py-4 font-semibold text-base transition-all duration-200', + 'group inline-flex items-center gap-3 px-10 py-4 font-semibold text-base transition-all duration-200 rounded-full', variant === 'primary' ? 'bg-brand text-white hover:bg-brand-hover' : 'text-ink border border-ink/20 hover:border-ink/40 hover:bg-ink/5', @@ -116,7 +159,7 @@ function CTAButton({ children, href, variant = 'primary', className, dataTestId ); } -// ===== Hero 区:克制白底 + 品牌红点缀 ===== +// ===== Hero 区:全出血深色画布(对标 Accenture 纯黑 Hero)+ 品牌红单电压 ===== function HeroSection({ heroData, stats }: { heroData?: Record | null; stats?: Record[]; @@ -138,12 +181,13 @@ function HeroSection({ heroData, stats }: { return (
- {/* 背景纹理:极淡的品牌色点阵 */} -
@@ -151,7 +195,7 @@ function HeroSection({ heroData, stats }: {
- {/* 品牌标识:Logo 本身已包含印章 + 公司名 + NOVALON */} + {/* 品牌标识:深色画布使用白色版 Logo(浅色版见 header/footer) */} {`${COMPANY_INFO.name} - {/* 品牌口号 Badge:消费站点配置 slogan,后台可改 */} + {/* 品牌口号 Badge:深色画布上的品牌红电压 */} {slogan && ( - + {slogan} )} - {/* 主标题:大胆、直接 */} + {/* 主标题:大胆、直接(深色画布上的白色宣言) */} {heading} - {/* 数据指标条:答案优先,数据先行 */} + {/* 数据指标条:答案优先,数据先行(品牌红仅用于第一个数字) */} {displayStats.length > 0 && ( {displayStats.map((stat, i) => (
- + {stat.value} - + {stat.label}
@@ -220,7 +264,7 @@ function HeroSection({ heroData, stats }: { initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.35, delay: 0.24, ease: EASE_OUT }} - className="text-lg sm:text-xl text-text-secondary max-w-2xl leading-relaxed mb-10" + className="text-lg sm:text-xl text-white/70 max-w-2xl leading-relaxed mb-10" > {subheading} @@ -238,7 +282,7 @@ function HeroSection({ heroData, stats }: { {secondaryCtaLabel} @@ -247,7 +291,7 @@ function HeroSection({ heroData, stats }: {
- {/* 右侧:产品视觉(SVG 数据看板示意) */} + {/* 右侧:产品视觉(SVG 数据看板示意,浅色卡片浮于深色画布) */} ; + + return ( +
+
+ ); +} + // ===== 服务区:等宽三列卡片 + 兜底数据 ===== function ServicesSection({ services, pageCopy }: { services?: Record[]; pageCopy?: HomePageCopy | null }) { const hasValidData = services?.length && (services![0] as any)?.subtitle; @@ -500,7 +622,59 @@ function ServicesSection({ services, pageCopy }: { services?: Record + {/* 背景纹理:极淡白色点阵(Bain 式深红区块 + 克制质感) */} +
+
+ +
+
+ + {eyebrow} + +
+
+
+
+ ); +} + +// ===== 共创流程(可验证方法论,不虚构客户)===== +const CO_CREATION_STEPS = [ + { step: '01', title: '业务诊断', desc: '深入理解业务场景,识别高价值切入点' }, + { step: '02', title: '方案共创', desc: '与核心团队共同设计解决方案与实施路径' }, + { step: '03', title: '验证发布', desc: '以真实运营数据验证成效,经授权后公开案例' }, +]; + +// ===== 案例区:Client Spotlight(对标 Accenture:深色大卡 spotlight + 其余小卡)===== function CasesSection({ cases, pageCopy }: { cases?: Record[]; pageCopy?: HomePageCopy | null }) { const hasValidData = cases?.length && cases[0]?.metrics; const CASE_ITEMS = hasValidData ? cases! : []; @@ -537,11 +711,46 @@ function CasesSection({ cases, pageCopy }: { cases?: Record[]; page -
-
- {earlyLabel} +
+ {/* 背景纹理:极淡白色点阵(对标埃森哲深色 spotlight 画布) */} +
+
+
+
+ + {casesEyebrow} + +
+
-

{earlyDesc}

@@ -550,6 +759,9 @@ function CasesSection({ cases, pageCopy }: { cases?: Record[]; page ); } + const [spotlightCase, ...restCases] = CASE_ITEMS; + const spotlightTestimonial = spotlightCase?.testimonial as Record | undefined; + return (
@@ -570,46 +782,191 @@ function CasesSection({ cases, pageCopy }: { cases?: Record[]; page
-
- {CASE_ITEMS.map((caseItem, i) => ( - - -
- - {caseItem.industry} - -
- -

- {caseItem.title} -

- -
- {caseItem.metrics.slice(0, 3).map((metric: Record, j: number) => ( -
-
- {metric.value} -
-
- {metric.label} -
-
- ))} -
- -
-
- 了解详情 - + {/* Client Spotlight 大卡:深色画布 + 客户引言 + 量化成果(对标 Accenture) */} + {spotlightCase && ( + + + {/* 背景纹理:极淡白色点阵 */} +
+ + + + )} + + {restCases.length > 0 && ( + + )} +
+
+ ); +} + +// ===== 新闻动态区(对标 Accenture News:最近 3 条 + 查看全部入口)===== +function NewsSection({ news, pageCopy }: { news?: NewsItem[]; pageCopy?: HomePageCopy | null }) { + const eyebrow = (pageCopy?.newsEyebrow as string) || '新闻动态'; + const titleLines = ((pageCopy?.newsTitle as string) || '公司要闻\n与研发动态').split('\n'); + const ctaLabel = (pageCopy?.newsCtaLabel as string) || '查看全部新闻'; + const NEWS_ITEMS = (news?.length ? news.slice(0, 3) : NEWS.slice(0, 3)) as NewsItem[]; + + return ( +
+
+
+ +
+
+ + {eyebrow} + +
+

+ +

+ + + + {ctaLabel} + + + +
+ +
+ {NEWS_ITEMS.map((item, i) => ( + + +
+ + {item.category} + + + +
+

+ {item.title} +

+

+ {item.excerpt} +

+
+ + 阅读全文 + + +
+
))}
@@ -642,16 +999,17 @@ function CTASection({ heroData, pageCopy }: { heroData?: Record | n } // ===== 主页面组件 ===== -export default function HomeContentV14({ services, cases, stats, heroData, pageCopy }: { +export default function HomeContentV15({ services, cases, stats, heroData, news, pageCopy }: { services?: Record[]; cases?: Record[]; stats?: Record[]; heroData?: Record | null; + news?: NewsItem[]; pageCopy?: HomePageCopy | null; }) { return (
- {/* 1. Hero:克制白底 + 品牌红点缀 */} + {/* 1. Hero:全出血深色画布 + 品牌红单电压(对标 Accenture 纯黑 Hero) */} {/* 2. 信任区:量化数据成果展示 */} @@ -660,14 +1018,23 @@ export default function HomeContentV14({ services, cases, stats, heroData, pageC {/* 3. 章节式叙事:问题 → 方法 → 结果 */} - {/* 4. 服务区:等宽卡片 + 兜底数据 */} + {/* 4. 行业洞察:思想领导力支柱(对标 Accenture Insights) */} + + + {/* 5. 服务区:等宽卡片 + 兜底数据 */} - {/* 5. 案例区:轻量快照卡片 */} + {/* 6. 创始人观点:人格化背书(对标 Accenture CEO Quote) */} + + + {/* 7. 案例区:轻量快照卡片 */} - {/* 6. CTA:克制强调,品牌红仅用于按钮 */} + {/* 8. 新闻动态:持续动态(对标 Accenture News) */} + + + {/* 9. CTA:克制强调,品牌红仅用于按钮 */}
); -} \ No newline at end of file +} diff --git a/src/app/(marketing)/page.test.tsx b/src/app/(marketing)/page.test.tsx index 39b72f9..1e6ef91 100644 --- a/src/app/(marketing)/page.test.tsx +++ b/src/app/(marketing)/page.test.tsx @@ -8,14 +8,15 @@ jest.mock('@/lib/cms/data-server', () => ({ getHomePageCopy: jest.fn(), })); -jest.mock('./home-content-v14', () => ({ +jest.mock('./home-content-v15', () => ({ __esModule: true, - default: function MockHomeContentV14(props: Record) { + default: function MockHomeContentV15(props: Record) { return (
{(props.services as unknown[])?.length ?? 0}
{(props.cases as unknown[])?.length ?? 0}
{(props.stats as unknown[])?.length ?? 0}
+
{(props.news as unknown[])?.length ?? 0}
{props.heroData ? JSON.stringify(props.heroData) : 'null'}
{props.pageCopy ? JSON.stringify(props.pageCopy) : 'null'}
@@ -90,6 +91,9 @@ describe('HomePage', () => { if (modelCode === 'hero-banner') { return Promise.resolve([createMockItem('hero-banner', { heading: 'Direct Hero' })]); } + if (modelCode === 'news') { + return Promise.resolve([createMockItem('news', { title: '公司新闻' })]); + } return Promise.resolve([]); }); @@ -98,6 +102,7 @@ describe('HomePage', () => { expect(screen.getByTestId('services-count')).toHaveTextContent('1'); expect(screen.getByTestId('hero-data')).toHaveTextContent('Direct Hero'); + expect(screen.getByTestId('news-count')).toHaveTextContent('1'); }); it('handles empty zones gracefully', async () => { diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index 67bfb47..8efd722 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,5 +1,6 @@ import { getPublishedItems, getResolvedHomeZones, getHomePageCopy } from '@/lib/cms/data-server'; -import HomeContentV14 from './home-content-v14'; +import type { NewsItem } from '@/lib/constants/news'; +import HomeContentV15 from './home-content-v15'; export const revalidate = 3600; @@ -11,6 +12,7 @@ export default async function HomePage() { let cases: Record[] = []; let stats: Record[] = []; let heroData: Record | null = null; + let news: NewsItem[] = []; if (hasZones) { services = zones.services?.map((item) => item.data) ?? []; @@ -18,27 +20,30 @@ export default async function HomePage() { stats = zones.stats?.map((item) => item.data) ?? []; heroData = zones.hero?.[0]?.data ?? null; } else { - const [serviceItems, caseItems, statItems, heroItems] = await Promise.all([ + const [serviceItems, caseItems, statItems, heroItems, newsItems] = await Promise.all([ getPublishedItems('service'), getPublishedItems('case-study'), getPublishedItems('stat-item'), getPublishedItems('hero-banner'), + getPublishedItems('news'), ]); services = serviceItems.map((item) => item.data); cases = caseItems.map((item) => item.data); stats = statItems.map((item) => item.data); heroData = heroItems[0]?.data ?? null; + news = newsItems.map((item) => item.data as unknown as NewsItem); } // 首页各章节结构性文案(信任信号/叙事情节/标题/CTA),CMS 未配置时为 null(组件内部兜底) const homePageCopy = await getHomePageCopy().catch(() => null); - return ; }