- Remove TestimonialSection from homepage (no customers yet) - Footer: dark theme, NAVIGATION_V2 + MEGA_DROPDOWN_DATA links - Mobile Menu: NAVIGATION_V2 with collapsible dropdown support
53 KiB
Atlassian 风格品牌融合重构 实现计划
面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(
- [ ])语法来跟踪进度。
目标: 借鉴 Atlassian 信息架构,重构 Novalon 首页为"收敛但保留印记"的品牌融合风格,同步沉淀 6 个核心组件作为设计系统基础。
架构: 保持 Next.js 16 App Router + 静态导出架构不变。首页从单文件长滚动重构为 6 个独立 Section 组件 + 新导航组件。动效从粒子/水墨收敛为微交互。导航从 8 项平铺改为 4 核心 + 2 下拉。
技术栈: Next.js 16 + React 19 + Tailwind CSS 4 + Framer Motion 12 + Lucide React
整体架构图
graph TB
subgraph "Phase 1: 首页重构 + 设计系统基础"
T1[任务1: Design Token 更新] --> T2[任务2: 导航常量重构]
T2 --> T3[任务3: MegaDropdown 组件]
T3 --> T4[任务4: Header 重构]
T4 --> T5[任务5: Hero Section 重构]
T5 --> T6[任务6: StatsBar 组件]
T6 --> T7[任务7: 社会证明 Section]
T7 --> T8[任务8: ProductCard 组件]
T8 --> T9[任务9: 产品矩阵 Section]
T9 --> T10[任务10: ChallengeCard 组件]
T10 --> T11[任务11: 挑战场景 Section]
T11 --> T12[任务12: TestimonialBlock 组件]
T12 --> T13[任务13: 客户成果 Section]
T13 --> T14[任务14: CTASection 组件]
T14 --> T15[任务15: CTA Section]
T15 --> T16[任务16: 首页组装]
T16 --> T17[任务17: 废弃动效清理]
T17 --> T18[任务18: 集成测试与验证]
end
组件依赖图
graph LR
subgraph "基础层"
DT[Design Tokens<br/>globals.css]
NAV_CONST[Navigation Constants<br/>navigation.ts]
end
subgraph "组件层"
MD[MegaDropdown<br/>mega-dropdown.tsx]
PC[ProductCard<br/>product-card.tsx]
CC[ChallengeCard<br/>challenge-card.tsx]
SB[StatsBar<br/>stats-bar.tsx]
TB[TestimonialBlock<br/>testimonial-block.tsx]
CTA[CTASection<br/>cta-section.tsx]
end
subgraph "Section 层"
HDR[Header<br/>header.tsx]
HERO[HeroSection<br/>hero-section.tsx]
SOCIAL[SocialProofSection<br/>social-proof-section.tsx]
PROD[ProductMatrixSection<br/>product-matrix-section.tsx]
CHAL[ChallengeSection<br/>challenge-section.tsx]
TEST[TestimonialSection<br/>testimonial-section.tsx]
CTAS[CTASectionPage<br/>cta-section-page.tsx]
end
subgraph "页面层"
HOME[home-content.tsx]
end
DT --> MD & PC & CC & SB & TB & CTA
NAV_CONST --> MD
MD --> HDR
PC --> PROD
CC --> CHAL
SB --> SOCIAL
TB --> TEST
CTA --> CTAS
HDR & HERO & SOCIAL & PROD & CHAL & TEST & CTAS --> HOME
数据流图
graph TD
subgraph "数据源 (src/lib/constants/)"
NAV[navigation.ts<br/>NAVIGATION_V2]
PRODS[products.ts<br/>PRODUCTS]
SOL[solutions.ts<br/>SOLUTIONS]
CASES[cases.ts<br/>CASES]
STATS[stats.ts<br/>STATS]
end
NAV -->|导航项+下拉数据| HDR_COMP[Header]
PRODS -->|4个产品| PROD_SEC[ProductMatrixSection]
SOL -->|3个挑战场景| CHAL_SEC[ChallengeSection]
CASES -->|testimonial+results| TEST_SEC[TestimonialSection]
STATS -->|3个关键数据| SOCIAL_SEC[SocialProofSection]
文件结构
新建文件
| 文件 | 职责 |
|---|---|
src/components/layout/mega-dropdown.tsx |
产品/解决方案矩阵下拉导航组件 |
src/components/layout/mega-dropdown.test.tsx |
下拉导航测试 |
src/components/ui/product-card.tsx |
朱砂红左边框产品卡片 |
src/components/ui/product-card.test.tsx |
产品卡片测试 |
src/components/ui/challenge-card.tsx |
痛点/场景入口卡片 |
src/components/ui/challenge-card.test.tsx |
场景卡片测试 |
src/components/ui/stats-bar.tsx |
三列数据统计条 |
src/components/ui/stats-bar.test.tsx |
统计条测试 |
src/components/ui/testimonial-block.tsx |
深色背景客户证言 |
src/components/ui/testimonial-block.test.tsx |
客户证言测试 |
src/components/ui/cta-section.tsx |
朱砂红渐变行动号召 |
src/components/ui/cta-section.test.tsx |
CTA 测试 |
src/components/sections/hero-section-v2.tsx |
新 Hero Section |
src/components/sections/hero-section-v2.test.tsx |
新 Hero 测试 |
src/components/sections/social-proof-section.tsx |
社会证明 Section |
src/components/sections/social-proof-section.test.tsx |
社会证明测试 |
src/components/sections/product-matrix-section.tsx |
产品矩阵 Section |
src/components/sections/product-matrix-section.test.tsx |
产品矩阵测试 |
src/components/sections/challenge-section.tsx |
挑战场景 Section |
src/components/sections/challenge-section.test.tsx |
挑战场景测试 |
src/components/sections/testimonial-section.tsx |
客户成果 Section |
src/components/sections/testimonial-section.test.tsx |
客户成果测试 |
src/components/sections/cta-section-page.tsx |
CTA Section |
src/components/sections/cta-section-page.test.tsx |
CTA Section 测试 |
修改文件
| 文件 | 变更内容 |
|---|---|
src/app/globals.css |
新增场景色 Token、废弃动效相关 CSS |
src/lib/constants/navigation.ts |
新增 NAVIGATION_V2 常量 + MegaDropdown 数据结构 |
src/lib/constants/stats.ts |
新增 HOME_STATS 三列数据 |
src/components/layout/header.tsx |
集成 MegaDropdown,使用 NAVIGATION_V2 |
src/app/(marketing)/home-content.tsx |
替换为新 Section 组件 |
src/components/sections/hero-section.tsx |
保留旧版,新版本为 hero-section-v2.tsx |
任务 1:Design Token 更新
文件:
-
修改:
src/app/globals.css -
步骤 1:在
:root中新增场景色变量
在 globals.css 的 :root 块中,在 /* 状态色 */ 注释之后添加:
/* 场景色 - 挑战卡片 */
--color-challenge-isolation: #FEF2F4;
--color-challenge-isolation-hover: #FDE8EC;
--color-challenge-growth: #FFFBEB;
--color-challenge-growth-hover: #FEF3C7;
--color-challenge-compliance: #F0FDF4;
--color-challenge-compliance-hover: #DCFCE7;
- 步骤 2:验证 CSS 变量可用
运行:npx tsc --noEmit
预期:无类型错误
- 步骤 3:Commit
git add src/app/globals.css
git commit -m "feat: add challenge scenario color tokens to design system"
任务 2:导航常量重构
文件:
-
修改:
src/lib/constants/navigation.ts -
步骤 1:编写失败的测试
在 src/lib/constants/navigation.test.ts(新建)中:
import { describe, it, expect } from '@jest/globals';
import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from './navigation';
describe('NAVIGATION_V2', () => {
it('has exactly 6 navigation items', () => {
expect(NAVIGATION_V2).toHaveLength(6);
});
it('contains product dropdown item', () => {
const productItem = NAVIGATION_V2.find(item => item.id === 'products');
expect(productItem).toBeDefined();
expect(productItem?.hasDropdown).toBe(true);
});
it('contains solutions dropdown item', () => {
const solutionsItem = NAVIGATION_V2.find(item => item.id === 'solutions');
expect(solutionsItem).toBeDefined();
expect(solutionsItem?.hasDropdown).toBe(true);
});
});
describe('MEGA_DROPDOWN_DATA', () => {
it('has products dropdown with 4 items', () => {
expect(MEGA_DROPDOWN_DATA.products).toHaveLength(4);
});
it('has solutions dropdown with 4 items', () => {
expect(MEGA_DROPDOWN_DATA.solutions).toHaveLength(4);
});
it('each product has id, title, description, href', () => {
MEGA_DROPDOWN_DATA.products.forEach(product => {
expect(product).toHaveProperty('id');
expect(product).toHaveProperty('title');
expect(product).toHaveProperty('description');
expect(product).toHaveProperty('href');
});
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/lib/constants/navigation.test.ts --no-coverage
预期:FAIL — Cannot find module './navigation' 或 NAVIGATION_V2 is not exported
- 步骤 3:实现导航常量
在 src/lib/constants/navigation.ts 中追加:
export interface NavigationItemV2 {
id: string;
label: string;
href: string;
hasDropdown?: boolean;
dropdownKey?: string;
}
export interface MegaDropdownItem {
id: string;
title: string;
description: string;
href: string;
}
export interface MegaDropdownData {
[key: string]: MegaDropdownItem[];
}
export const NAVIGATION_V2: NavigationItemV2[] = [
{ id: 'products', label: '产品', href: '/products', hasDropdown: true, dropdownKey: 'products' },
{ id: 'solutions', label: '解决方案', href: '/solutions', hasDropdown: true, dropdownKey: 'solutions' },
{ id: 'services', label: '服务', href: '/services' },
{ id: 'cases', label: '案例', href: '/cases' },
{ id: 'about', label: '关于我们', href: '/about' },
{ id: 'contact', label: '联系我们', href: '/contact' },
];
export const MEGA_DROPDOWN_DATA: MegaDropdownData = {
products: [
{ id: 'erp', title: 'ERP 管理系统', description: '财务·采购·销售·库存·生产', href: '/products/erp' },
{ id: 'crm', title: 'CRM 客户管理', description: '线索·商机·合同·服务', href: '/products/crm' },
{ id: 'bi', title: 'BI 数据平台', description: '报表·仪表盘·预测·决策', href: '/products/bi' },
{ id: 'cms', title: 'CMS 内容平台', description: '建站·运营·分发·分析', href: '/products/cms' },
],
solutions: [
{ id: 'manufacturing', title: '制造业', description: '智能制造·MES·质量管控', href: '/solutions/manufacturing' },
{ id: 'retail', title: '零售业', description: '全渠道·会员·精准营销', href: '/solutions/retail' },
{ id: 'education', title: '教育行业', description: '招生·教学·学情分析', href: '/solutions/education' },
{ id: 'healthcare', title: '医疗健康', description: '临床·运营·患者服务', href: '/solutions/healthcare' },
],
};
- 步骤 4:更新 index.ts 导出
在 src/lib/constants/index.ts 中追加导出:
export { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from './navigation';
export type { NavigationItemV2, MegaDropdownItem, MegaDropdownData } from './navigation';
- 步骤 5:运行测试验证通过
运行:npx jest src/lib/constants/navigation.test.ts --no-coverage
预期:PASS
- 步骤 6:Commit
git add src/lib/constants/navigation.ts src/lib/constants/navigation.test.ts src/lib/constants/index.ts
git commit -m "feat: add NAVIGATION_V2 and MEGA_DROPDOWN_DATA for mega dropdown navigation"
任务 3:MegaDropdown 组件
文件:
-
创建:
src/components/layout/mega-dropdown.tsx -
创建:
src/components/layout/mega-dropdown.test.tsx -
步骤 1:编写失败的测试
// src/components/layout/mega-dropdown.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { MegaDropdown } from './mega-dropdown';
import { MEGA_DROPDOWN_DATA } from '@/lib/constants';
describe('MegaDropdown', () => {
it('renders trigger button with label', () => {
render(
<MegaDropdown
label="产品"
items={MEGA_DROPDOWN_DATA.products}
isOpen={false}
onToggle={vi.fn()}
/>
);
expect(screen.getByText('产品')).toBeInTheDocument();
});
it('shows dropdown content when isOpen is true', () => {
render(
<MegaDropdown
label="产品"
items={MEGA_DROPDOWN_DATA.products}
isOpen={true}
onToggle={vi.fn()}
/>
);
expect(screen.getByText('ERP 管理系统')).toBeInTheDocument();
});
it('hides dropdown content when isOpen is false', () => {
render(
<MegaDropdown
label="产品"
items={MEGA_DROPDOWN_DATA.products}
isOpen={false}
onToggle={vi.fn()}
/>
);
expect(screen.queryByText('ERP 管理系统')).not.toBeInTheDocument();
});
it('calls onToggle when trigger is clicked', () => {
const onToggle = vi.fn();
render(
<MegaDropdown
label="产品"
items={MEGA_DROPDOWN_DATA.products}
isOpen={false}
onToggle={onToggle}
/>
);
fireEvent.click(screen.getByText('产品'));
expect(onToggle).toHaveBeenCalledTimes(1);
});
it('renders each item with title, description and link', () => {
render(
<MegaDropdown
label="产品"
items={MEGA_DROPDOWN_DATA.products}
isOpen={true}
onToggle={vi.fn()}
/>
);
MEGA_DROPDOWN_DATA.products.forEach(item => {
expect(screen.getByText(item.title)).toBeInTheDocument();
expect(screen.getByText(item.description)).toBeInTheDocument();
});
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/layout/mega-dropdown.test.ts --no-coverage
预期:FAIL
- 步骤 3:实现 MegaDropdown 组件
// src/components/layout/mega-dropdown.tsx
'use client';
import { useRef, useEffect } from 'react';
import { ChevronDown } from 'lucide-react';
import { AnimatePresence, motion } from 'framer-motion';
import { StaticLink } from '@/components/ui/static-link';
import type { MegaDropdownItem } from '@/lib/constants';
interface MegaDropdownProps {
label: string;
items: MegaDropdownItem[];
isOpen: boolean;
onToggle: () => void;
}
export function MegaDropdown({ label, items, isOpen, onToggle }: MegaDropdownProps) {
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
if (isOpen) { onToggle(); }
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [isOpen, onToggle]);
return (
<div ref={dropdownRef} className="relative">
<button
onClick={onToggle}
className="flex items-center gap-1 text-sm font-medium text-[#1C1C1C] hover:text-[#C41E3A] transition-colors duration-200"
aria-expanded={isOpen}
aria-haspopup="true"
>
{label}
<ChevronDown
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
/>
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.15 }}
className="absolute top-full left-0 mt-2 w-[480px] bg-white rounded-xl border border-[#E5E5E5] shadow-lg p-4 z-50"
>
<div className="grid grid-cols-2 gap-2">
{items.map((item) => (
<StaticLink
key={item.id}
href={item.href}
className="block p-3 rounded-lg border-l-[3px] border-l-[#C41E3A] hover:bg-[#FFFBF5] transition-colors duration-200"
>
<div className="text-sm font-semibold text-[#1C1C1C]">{item.title}</div>
<div className="text-xs text-[#5C5C5C] mt-1">{item.description}</div>
</StaticLink>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/layout/mega-dropdown.test.ts --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/layout/mega-dropdown.tsx src/components/layout/mega-dropdown.test.tsx
git commit -m "feat: add MegaDropdown component for product/solution matrix navigation"
任务 4:Header 重构
文件:
-
修改:
src/components/layout/header.tsx -
步骤 1:编写失败的测试
在 src/components/layout/header.test.tsx 中追加测试:
it('renders NAVIGATION_V2 items in desktop nav', () => {
render(<Header />);
expect(screen.getByText('产品')).toBeInTheDocument();
expect(screen.getByText('解决方案')).toBeInTheDocument();
expect(screen.getByText('服务')).toBeInTheDocument();
expect(screen.getByText('案例')).toBeInTheDocument();
expect(screen.getByText('关于我们')).toBeInTheDocument();
expect(screen.getByText('联系我们')).toBeInTheDocument();
});
it('does not render old navigation items (核心业务, 新闻动态)', () => {
render(<Header />);
expect(screen.queryByText('核心业务')).not.toBeInTheDocument();
expect(screen.queryByText('新闻动态')).not.toBeInTheDocument();
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/layout/header.test.tsx --no-coverage
预期:FAIL — 新测试用例找不到"产品"等新导航项
- 步骤 3:重构 Header 组件
在 header.tsx 中:
- 将
NAVIGATIONimport 替换为NAVIGATION_V2, MEGA_DROPDOWN_DATA - 将
MegaDropdownimport 添加 - 桌面导航渲染逻辑改为:遍历
NAVIGATION_V2,如果hasDropdown则渲染MegaDropdown,否则渲染普通链接 - 移除旧的
NAVIGATION引用 - 添加
openDropdownstate 管理下拉开关
关键代码片段(替换桌面导航部分):
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
// 在导航渲染中:
{NAVIGATION_V2.map((item) => (
item.hasDropdown ? (
<MegaDropdown
key={item.id}
label={item.label}
items={MEGA_DROPDOWN_DATA[item.dropdownKey!]}
isOpen={openDropdown === item.id}
onToggle={() => setOpenDropdown(openDropdown === item.id ? null : item.id)}
/>
) : (
<StaticLink
key={item.id}
href={item.href}
className={`text-sm font-medium transition-colors duration-200 ${
activeSection === item.id ? 'text-[#C41E3A]' : 'text-[#1C1C1C] hover:text-[#C41E3A]'
}`}
>
{item.label}
</StaticLink>
)
))}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/layout/header.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/layout/header.tsx src/components/layout/header.test.tsx
git commit -m "feat: refactor Header with NAVIGATION_V2 and MegaDropdown integration"
任务 5:Hero Section 重构
文件:
-
创建:
src/components/sections/hero-section-v2.tsx -
创建:
src/components/sections/hero-section-v2.test.tsx -
步骤 1:编写失败的测试
// src/components/sections/hero-section-v2.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { HeroSectionV2 } from './hero-section-v2';
describe('HeroSectionV2', () => {
it('renders brand tag NOVALON', () => {
render(<HeroSectionV2 />);
expect(screen.getByText(/NOVALON/)).toBeInTheDocument();
});
it('renders main heading with calligraphy', () => {
render(<HeroSectionV2 />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toBeInTheDocument();
expect(heading.textContent).toContain('智连未来');
});
it('renders two CTA buttons', () => {
render(<HeroSectionV2 />);
expect(screen.getByText('预约演示')).toBeInTheDocument();
expect(screen.getByText('了解方案')).toBeInTheDocument();
});
it('renders subtitle text', () => {
render(<HeroSectionV2 />);
expect(screen.getByText(/从战略规划到系统落地/)).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/sections/hero-section-v2.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 HeroSectionV2
// src/components/sections/hero-section-v2.tsx
'use client';
import { useEffect, useRef, useState } from 'react';
import { motion } from 'framer-motion';
import { StaticLink } from '@/components/ui/static-link';
export function HeroSectionV2() {
const [isVisible, setIsVisible] = useState(false);
const sectionRef = useRef<HTMLElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) { setIsVisible(true); }
},
{ threshold: 0.1 }
);
if (sectionRef.current) { observer.observe(sectionRef.current); }
return () => observer.disconnect();
}, []);
return (
<section
id="home"
ref={sectionRef}
aria-labelledby="hero-heading"
className="relative min-h-[85vh] flex items-center overflow-hidden bg-gradient-to-b from-[#FFFBF5] to-white"
>
<div
className="absolute top-12 right-16 w-24 h-24 border border-[rgba(196,30,58,0.08)] rounded-full"
aria-hidden="true"
/>
<div
className="absolute bottom-16 left-10 w-18 h-18 border border-[rgba(196,30,58,0.06)] rounded-full"
aria-hidden="true"
/>
<div className="container-wide py-24 md:py-32 lg:py-40 relative z-10">
<div className="max-w-3xl mx-auto text-center">
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={isVisible ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-xs tracking-[0.2em] text-[#C41E3A] mb-6"
>
NOVALON · 睿新致遠
</motion.p>
<motion.h1
id="hero-heading"
initial={{ opacity: 0, y: 20 }}
animate={isVisible ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2 }}
className="font-calligraphy text-4xl md:text-5xl lg:text-6xl text-[#1C1C1C] tracking-[0.05em] mb-6"
>
智连未来,成长伙伴
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={isVisible ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.3 }}
className="text-base md:text-lg text-[#5C5C5C] max-w-xl mx-auto mb-10"
>
从战略规划到系统落地,陪伴企业完成数字化转型的每一步
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isVisible ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.4 }}
className="flex flex-col sm:flex-row gap-4 justify-center"
>
<StaticLink
href="/contact"
className="inline-flex items-center justify-center px-8 py-3 bg-[#C41E3A] text-white text-sm font-medium rounded hover:bg-[#A01830] transition-colors duration-200"
>
预约演示
</StaticLink>
<StaticLink
href="/solutions"
className="inline-flex items-center justify-center px-8 py-3 border border-[#1C1C1C] text-[#1C1C1C] text-sm font-medium rounded hover:bg-[#1C1C1C] hover:text-white transition-colors duration-200"
>
了解方案
</StaticLink>
</motion.div>
</div>
</div>
</section>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/sections/hero-section-v2.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/sections/hero-section-v2.tsx src/components/sections/hero-section-v2.test.tsx
git commit -m "feat: add HeroSectionV2 with brand fusion style and dual CTAs"
任务 6:StatsBar 组件
文件:
-
创建:
src/components/ui/stats-bar.tsx -
创建:
src/components/ui/stats-bar.test.tsx -
步骤 1:编写失败的测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { StatsBar } from './stats-bar';
const mockStats = [
{ value: '200+', label: '企业客户' },
{ value: '15+', label: '行业覆盖' },
{ value: '99.9%', label: '系统可用性' },
];
describe('StatsBar', () => {
it('renders all stat items', () => {
render(<StatsBar stats={mockStats} />);
expect(screen.getByText('200+')).toBeInTheDocument();
expect(screen.getByText('15+')).toBeInTheDocument();
expect(screen.getByText('99.9%')).toBeInTheDocument();
});
it('renders all labels', () => {
render(<StatsBar stats={mockStats} />);
expect(screen.getByText('企业客户')).toBeInTheDocument();
expect(screen.getByText('行业覆盖')).toBeInTheDocument();
expect(screen.getByText('系统可用性')).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/ui/stats-bar.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 StatsBar
// src/components/ui/stats-bar.tsx
'use client';
import { AnimatedNumber } from './animated-number';
export interface StatItem {
value: string;
label: string;
}
interface StatsBarProps {
stats: StatItem[];
}
export function StatsBar({ stats }: StatsBarProps) {
return (
<div className="grid grid-cols-3 gap-8">
{stats.map((stat) => (
<div key={stat.label} className="text-center">
<div className="text-3xl md:text-4xl font-bold text-[#C41E3A]">
{stat.value}
</div>
<div className="text-sm text-[#5C5C5C] mt-1">{stat.label}</div>
</div>
))}
</div>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/ui/stats-bar.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/ui/stats-bar.tsx src/components/ui/stats-bar.test.tsx
git commit -m "feat: add StatsBar component for social proof data display"
任务 7:社会证明 Section
文件:
-
创建:
src/components/sections/social-proof-section.tsx -
创建:
src/components/sections/social-proof-section.test.tsx -
修改:
src/lib/constants/stats.ts -
步骤 1:在 stats.ts 中新增 HOME_STATS
export const HOME_STATS: StatItem[] = [
{ value: '200+', label: '企业客户' },
{ value: '15+', label: '行业覆盖' },
{ value: '99.9%', label: '系统可用性' },
];
- 步骤 2:编写测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { SocialProofSection } from './social-proof-section';
describe('SocialProofSection', () => {
it('renders section with stats', () => {
render(<SocialProofSection />);
expect(screen.getByText('200+')).toBeInTheDocument();
expect(screen.getByText('企业客户')).toBeInTheDocument();
});
it('has correct background class', () => {
const { container } = render(<SocialProofSection />);
const section = container.querySelector('section');
expect(section?.className).toContain('bg-[#F5F5F5]');
});
});
- 步骤 3:实现 SocialProofSection
// src/components/sections/social-proof-section.tsx
'use client';
import { StatsBar } from '@/components/ui/stats-bar';
import { HOME_STATS } from '@/lib/constants/stats';
export function SocialProofSection() {
return (
<section className="bg-[#F5F5F5] py-16 md:py-20" aria-label="社会证明">
<div className="container-wide">
<StatsBar stats={HOME_STATS} />
</div>
</section>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/sections/social-proof-section.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/sections/social-proof-section.tsx src/components/sections/social-proof-section.test.tsx src/lib/constants/stats.ts
git commit -m "feat: add SocialProofSection with HOME_STATS data"
任务 8:ProductCard 组件
文件:
-
创建:
src/components/ui/product-card.tsx -
创建:
src/components/ui/product-card.test.tsx -
步骤 1:编写失败的测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ProductCard } from './product-card';
const mockProduct = {
id: 'erp',
title: 'ERP 管理系统',
description: '财务·采购·销售·库存·生产',
href: '/products/erp',
};
describe('ProductCard', () => {
it('renders product title', () => {
render(<ProductCard {...mockProduct} />);
expect(screen.getByText('ERP 管理系统')).toBeInTheDocument();
});
it('renders product description', () => {
render(<ProductCard {...mockProduct} />);
expect(screen.getByText('财务·采购·销售·库存·生产')).toBeInTheDocument();
});
it('renders link to product page', () => {
render(<ProductCard {...mockProduct} />);
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/products/erp');
});
it('has left border accent', () => {
const { container } = render(<ProductCard {...mockProduct} />);
const card = container.firstChild as HTMLElement;
expect(card.className).toContain('border-l-[3px]');
expect(card.className).toContain('border-l-[#C41E3A]');
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/ui/product-card.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 ProductCard
// src/components/ui/product-card.tsx
import { StaticLink } from '@/components/ui/static-link';
interface ProductCardProps {
id: string;
title: string;
description: string;
href: string;
}
export function ProductCard({ title, description, href }: ProductCardProps) {
return (
<StaticLink
href={href}
className="block p-5 rounded-lg border border-[#E5E5E5] border-l-[3px] border-l-[#C41E3A] bg-white hover:bg-[#FFFBF5] hover:shadow-md hover:-translate-y-0.5 transition-all duration-200"
>
<h3 className="text-base font-semibold text-[#1C1C1C]">{title}</h3>
<p className="text-sm text-[#5C5C5C] mt-1.5">{description}</p>
</StaticLink>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/ui/product-card.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/ui/product-card.tsx src/components/ui/product-card.test.tsx
git commit -m "feat: add ProductCard component with brand-red left border"
任务 9:产品矩阵 Section
文件:
-
创建:
src/components/sections/product-matrix-section.tsx -
创建:
src/components/sections/product-matrix-section.test.tsx -
步骤 1:编写测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ProductMatrixSection } from './product-matrix-section';
describe('ProductMatrixSection', () => {
it('renders section heading', () => {
render(<ProductMatrixSection />);
expect(screen.getByText('核心产品')).toBeInTheDocument();
});
it('renders all 4 products', () => {
render(<ProductMatrixSection />);
expect(screen.getByText('ERP 管理系统')).toBeInTheDocument();
expect(screen.getByText('CRM 客户管理')).toBeInTheDocument();
expect(screen.getByText('BI 数据平台')).toBeInTheDocument();
expect(screen.getByText('CMS 内容平台')).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/sections/product-matrix-section.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 ProductMatrixSection
// src/components/sections/product-matrix-section.tsx
'use client';
import { ProductCard } from '@/components/ui/product-card';
import { PRODUCTS } from '@/lib/constants/products';
export function ProductMatrixSection() {
return (
<section className="bg-white py-20 md:py-24" aria-label="核心产品">
<div className="container-wide">
<h2 className="text-2xl md:text-3xl font-bold text-[#1C1C1C] mb-10">核心产品</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{PRODUCTS.map((product) => (
<ProductCard
key={product.id}
id={product.id}
title={product.title}
description={product.description.split(',')[0]}
href={`/products/${product.id}`}
/>
))}
</div>
</div>
</section>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/sections/product-matrix-section.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/sections/product-matrix-section.tsx src/components/sections/product-matrix-section.test.tsx
git commit -m "feat: add ProductMatrixSection with 2x2 product card grid"
任务 10:ChallengeCard 组件
文件:
-
创建:
src/components/ui/challenge-card.tsx -
创建:
src/components/ui/challenge-card.test.tsx -
步骤 1:编写失败的测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ChallengeCard } from './challenge-card';
const mockChallenge = {
id: 'isolation',
icon: '🏭',
title: '系统孤岛',
subtitle: '数据不通',
bgColor: '#FEF2F4',
hoverBgColor: '#FDE8EC',
href: '/solutions/manufacturing',
};
describe('ChallengeCard', () => {
it('renders icon', () => {
render(<ChallengeCard {...mockChallenge} />);
expect(screen.getByText('🏭')).toBeInTheDocument();
});
it('renders title and subtitle', () => {
render(<ChallengeCard {...mockChallenge} />);
expect(screen.getByText('系统孤岛')).toBeInTheDocument();
expect(screen.getByText('数据不通')).toBeInTheDocument();
});
it('renders link to solution page', () => {
render(<ChallengeCard {...mockChallenge} />);
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/solutions/manufacturing');
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/ui/challenge-card.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 ChallengeCard
// src/components/ui/challenge-card.tsx
import { StaticLink } from '@/components/ui/static-link';
interface ChallengeCardProps {
id: string;
icon: string;
title: string;
subtitle: string;
bgColor: string;
hoverBgColor: string;
href: string;
}
export function ChallengeCard({ icon, title, subtitle, bgColor, hoverBgColor, href }: ChallengeCardProps) {
return (
<StaticLink
href={href}
className="block p-6 rounded-xl text-center transition-all duration-200 hover:shadow-md hover:-translate-y-0.5"
style={{ backgroundColor: bgColor }}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = hoverBgColor; }}
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = bgColor; }}
>
<div className="text-3xl mb-3">{icon}</div>
<h3 className="text-sm font-bold text-[#1C1C1C]">{title}</h3>
<p className="text-xs text-[#C41E3A] mt-1">{subtitle}</p>
</StaticLink>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/ui/challenge-card.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/ui/challenge-card.tsx src/components/ui/challenge-card.test.tsx
git commit -m "feat: add ChallengeCard component for scenario entry points"
任务 11:挑战场景 Section
文件:
-
创建:
src/components/sections/challenge-section.tsx -
创建:
src/components/sections/challenge-section.test.tsx -
步骤 1:编写测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ChallengeSection } from './challenge-section';
describe('ChallengeSection', () => {
it('renders section heading', () => {
render(<ChallengeSection />);
expect(screen.getByText('您面临什么挑战?')).toBeInTheDocument();
});
it('renders three challenge cards', () => {
render(<ChallengeSection />);
expect(screen.getByText('系统孤岛')).toBeInTheDocument();
expect(screen.getByText('增长瓶颈')).toBeInTheDocument();
expect(screen.getByText('合规风险')).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/sections/challenge-section.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 ChallengeSection
// src/components/sections/challenge-section.tsx
'use client';
import { ChallengeCard } from '@/components/ui/challenge-card';
const CHALLENGES = [
{
id: 'isolation',
icon: '🏭',
title: '系统孤岛',
subtitle: '数据不通',
bgColor: 'var(--color-challenge-isolation)',
hoverBgColor: 'var(--color-challenge-isolation-hover)',
href: '/solutions/manufacturing',
},
{
id: 'growth',
icon: '📈',
title: '增长瓶颈',
subtitle: '效率低下',
bgColor: 'var(--color-challenge-growth)',
hoverBgColor: 'var(--color-challenge-growth-hover)',
href: '/solutions/retail',
},
{
id: 'compliance',
icon: '🔒',
title: '合规风险',
subtitle: '安全隐忧',
bgColor: 'var(--color-challenge-compliance)',
hoverBgColor: 'var(--color-challenge-compliance-hover)',
href: '/solutions/healthcare',
},
];
export function ChallengeSection() {
return (
<section className="bg-[#FFFBF5] py-20 md:py-24" aria-label="挑战与场景">
<div className="container-wide">
<h2 className="text-2xl md:text-3xl font-bold text-[#1C1C1C] mb-10">您面临什么挑战?</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{CHALLENGES.map((challenge) => (
<ChallengeCard key={challenge.id} {...challenge} />
))}
</div>
</div>
</section>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/sections/challenge-section.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/sections/challenge-section.tsx src/components/sections/challenge-section.test.tsx
git commit -m "feat: add ChallengeSection with three scenario entry cards"
任务 12:TestimonialBlock 组件
文件:
-
创建:
src/components/ui/testimonial-block.tsx -
创建:
src/components/ui/testimonial-block.test.tsx -
步骤 1:编写失败的测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { TestimonialBlock } from './testimonial-block';
const mockTestimonial = {
quote: '与睿新合作后,运营效率提升了40%',
author: '某上市制造企业 CTO',
results: [
{ value: '40%', label: '效率提升' },
{ value: '6月', label: '交付周期' },
],
};
describe('TestimonialBlock', () => {
it('renders quote text', () => {
render(<TestimonialBlock {...mockTestimonial} />);
expect(screen.getByText(/运营效率提升了40%/)).toBeInTheDocument();
});
it('renders author attribution', () => {
render(<TestimonialBlock {...mockTestimonial} />);
expect(screen.getByText(/某上市制造企业 CTO/)).toBeInTheDocument();
});
it('renders result metrics', () => {
render(<TestimonialBlock {...mockTestimonial} />);
expect(screen.getByText('40%')).toBeInTheDocument();
expect(screen.getByText('效率提升')).toBeInTheDocument();
expect(screen.getByText('6月')).toBeInTheDocument();
expect(screen.getByText('交付周期')).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/ui/testimonial-block.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 TestimonialBlock
// src/components/ui/testimonial-block.tsx
interface TestimonialResult {
value: string;
label: string;
}
interface TestimonialBlockProps {
quote: string;
author: string;
results: TestimonialResult[];
}
export function TestimonialBlock({ quote, author, results }: TestimonialBlockProps) {
return (
<div className="bg-[#1C1C1C] rounded-xl p-8 md:p-10">
<p className="text-xs text-[rgba(255,255,255,0.6)] mb-4">客户成果</p>
<blockquote className="text-base md:text-lg text-white italic mb-4">
“{quote}”
</blockquote>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<p className="text-xs text-[rgba(255,255,255,0.5)]">— {author}</p>
<div className="flex gap-6">
{results.map((result) => (
<div key={result.label} className="text-center">
<div className="text-lg font-bold text-[#C41E3A]">{result.value}</div>
<div className="text-[10px] text-[rgba(255,255,255,0.4)]">{result.label}</div>
</div>
))}
</div>
</div>
</div>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/ui/testimonial-block.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/ui/testimonial-block.tsx src/components/ui/testimonial-block.test.tsx
git commit -m "feat: add TestimonialBlock component with dark background and metrics"
任务 13:客户成果 Section
文件:
-
创建:
src/components/sections/testimonial-section.tsx -
创建:
src/components/sections/testimonial-section.test.tsx -
步骤 1:编写测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { TestimonialSection } from './testimonial-section';
describe('TestimonialSection', () => {
it('renders client testimonial quote', () => {
render(<TestimonialSection />);
expect(screen.getByText(/客户成果/)).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/sections/testimonial-section.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 TestimonialSection
// src/components/sections/testimonial-section.tsx
'use client';
import { TestimonialBlock } from '@/components/ui/testimonial-block';
import { CASES } from '@/lib/constants/cases';
export function TestimonialSection() {
const featuredCase = CASES.find(c => c.testimonial) || CASES[0];
const testimonial = featuredCase.testimonial
? {
quote: featuredCase.testimonial.quote,
author: `${featuredCase.testimonial.author},${featuredCase.testimonial.role}`,
results: featuredCase.results.slice(0, 3),
}
: {
quote: featuredCase.description.slice(0, 60) + '...',
author: featuredCase.client,
results: featuredCase.results.slice(0, 3),
};
return (
<section className="bg-[#1C1C1C] py-20 md:py-24" aria-label="客户成果">
<div className="container-wide">
<TestimonialBlock {...testimonial} />
</div>
</section>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/sections/testimonial-section.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/sections/testimonial-section.tsx src/components/sections/testimonial-section.test.tsx
git commit -m "feat: add TestimonialSection using CASES data for client stories"
任务 14:CTASection 组件
文件:
-
创建:
src/components/ui/cta-section.tsx -
创建:
src/components/ui/cta-section.test.tsx -
步骤 1:编写失败的测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { CTASection } from './cta-section';
describe('CTASection', () => {
it('renders heading', () => {
render(<CTASection heading="开启数字化转型之旅" />);
expect(screen.getByText('开启数字化转型之旅')).toBeInTheDocument();
});
it('renders subtitle when provided', () => {
render(<CTASection heading="开启" subtitle="免费咨询 · 专属方案" />);
expect(screen.getByText('免费咨询 · 专属方案')).toBeInTheDocument();
});
it('renders CTA button', () => {
render(<CTASection heading="开启" buttonText="立即咨询" buttonHref="/contact" />);
const link = screen.getByText('立即咨询');
expect(link).toBeInTheDocument();
expect(link.closest('a')).toHaveAttribute('href', '/contact');
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/ui/cta-section.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 CTASection
// src/components/ui/cta-section.tsx
import { StaticLink } from '@/components/ui/static-link';
interface CTASectionProps {
heading: string;
subtitle?: string;
buttonText?: string;
buttonHref?: string;
}
export function CTASection({
heading,
subtitle,
buttonText = '立即咨询',
buttonHref = '/contact',
}: CTASectionProps) {
return (
<div className="bg-gradient-to-br from-[#C41E3A] to-[#A01830] rounded-xl p-10 md:p-14 text-center">
<h2 className="text-xl md:text-2xl font-bold text-white mb-3">{heading}</h2>
{subtitle && (
<p className="text-sm text-[rgba(255,255,255,0.8)] mb-8">{subtitle}</p>
)}
<StaticLink
href={buttonHref}
className="inline-flex items-center justify-center px-8 py-3 border border-white text-white text-sm font-medium rounded hover:bg-white hover:text-[#C41E3A] transition-colors duration-200"
>
{buttonText}
</StaticLink>
</div>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/ui/cta-section.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/ui/cta-section.tsx src/components/ui/cta-section.test.tsx
git commit -m "feat: add CTASection component with brand-red gradient"
任务 15:CTA Section 页面级
文件:
-
创建:
src/components/sections/cta-section-page.tsx -
创建:
src/components/sections/cta-section-page.test.tsx -
步骤 1:编写测试
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { CTASectionPage } from './cta-section-page';
describe('CTASectionPage', () => {
it('renders CTA with correct content', () => {
render(<CTASectionPage />);
expect(screen.getByText('开启数字化转型之旅')).toBeInTheDocument();
expect(screen.getByText('免费咨询 · 专属方案 · 30天试用')).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/components/sections/cta-section-page.test.tsx --no-coverage
预期:FAIL
- 步骤 3:实现 CTASectionPage
// src/components/sections/cta-section-page.tsx
import { CTASection } from '@/components/ui/cta-section';
export function CTASectionPage() {
return (
<section className="py-20 md:py-24" aria-label="行动号召">
<div className="container-wide">
<CTASection
heading="开启数字化转型之旅"
subtitle="免费咨询 · 专属方案 · 30天试用"
buttonText="立即咨询"
buttonHref="/contact"
/>
</div>
</section>
);
}
- 步骤 4:运行测试验证通过
运行:npx jest src/components/sections/cta-section-page.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/components/sections/cta-section-page.tsx src/components/sections/cta-section-page.test.tsx
git commit -m "feat: add CTASectionPage with homepage-specific CTA content"
任务 16:首页组装
文件:
-
修改:
src/app/(marketing)/home-content.tsx -
步骤 1:编写测试
在 src/app/(marketing)/page.test.tsx(如不存在则新建)中:
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import HomeContent from './home-content';
describe('HomeContent V2', () => {
it('renders new hero section', () => {
render(<HomeContent heroStats={null} />);
expect(screen.getByText('智连未来,成长伙伴')).toBeInTheDocument();
});
it('renders product matrix section', () => {
render(<HomeContent heroStats={null} />);
expect(screen.getByText('核心产品')).toBeInTheDocument();
});
it('renders challenge section', () => {
render(<HomeContent heroStats={null} />);
expect(screen.getByText('您面临什么挑战?')).toBeInTheDocument();
});
it('renders CTA section', () => {
render(<HomeContent heroStats={null} />);
expect(screen.getByText('开启数字化转型之旅')).toBeInTheDocument();
});
});
- 步骤 2:运行测试验证失败
运行:npx jest src/app/\(marketing\)/page.test.tsx --no-coverage
预期:FAIL — 找不到新 Section 内容
- 步骤 3:重构 home-content.tsx
替换 home-content.tsx 的全部内容:
'use client';
import dynamic from 'next/dynamic';
import { HeroSectionV2 } from '@/components/sections/hero-section-v2';
import { SectionSkeleton } from '@/components/ui/loading-skeleton';
import type { ReactNode } from 'react';
const SocialProofSection = dynamic(
() => import('@/components/sections/social-proof-section').then(mod => ({ default: mod.SocialProofSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const ProductMatrixSection = dynamic(
() => import('@/components/sections/product-matrix-section').then(mod => ({ default: mod.ProductMatrixSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const ChallengeSection = dynamic(
() => import('@/components/sections/challenge-section').then(mod => ({ default: mod.ChallengeSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const TestimonialSection = dynamic(
() => import('@/components/sections/testimonial-section').then(mod => ({ default: mod.TestimonialSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const CTASectionPage = dynamic(
() => import('@/components/sections/cta-section-page').then(mod => ({ default: mod.CTASectionPage })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
function HomeContent({ heroStats }: { heroStats: ReactNode }) {
return (
<>
<HeroSectionV2 />
<SocialProofSection />
<ProductMatrixSection />
<ChallengeSection />
<TestimonialSection />
<CTASectionPage />
</>
);
}
export default HomeContent;
- 步骤 4:运行测试验证通过
运行:npx jest src/app/\(marketing\)/page.test.tsx --no-coverage
预期:PASS
- 步骤 5:Commit
git add src/app/\(marketing\)/home-content.tsx src/app/\(marketing\)/page.test.tsx
git commit -m "feat: assemble homepage with new brand-fusion sections"
任务 17:废弃动效清理
文件:
-
修改:
src/components/effects/index.ts -
步骤 1:从 effects/index.ts 移除废弃组件导出
移除以下导出:
InkBackground/InkTechFusionDataParticleFlowParticleGalaxyMouseInteractiveParticlesFluidWaveBackgroundGeometricAbstractGridLinesAdvancedFloatingEffects
保留以下导出:
-
SubtleDots -
GradientFlow/GradientFlowOptimized -
GradientOrbs -
GlowEffect -
ParallaxEffect -
SealAnimationEnhanced -
步骤 2:验证构建无报错
运行:npx tsc --noEmit
预期:无类型错误(如果有引用废弃组件的文件,需更新引用)
- 步骤 3:Commit
git add src/components/effects/index.ts
git commit -m "chore: remove deprecated particle/ink effect exports from index"
任务 18:集成测试与验证
- 步骤 1:运行全量单元测试
运行:npx jest --no-coverage
预期:所有测试 PASS
- 步骤 2:运行类型检查
运行:npx tsc --noEmit
预期:无类型错误
- 步骤 3:运行构建
运行:npm run build
预期:构建成功
- 步骤 4:启动开发服务器,手动验证首页
运行:npm run dev
验证项:
- 导航栏显示 6 项,"产品"和"解决方案"有下拉
- Hero 白底 + 书法标题 + 双 CTA
- 社会证明三列数字
- 产品矩阵 2×2 卡片 + 朱砂红左边框
- 挑战场景三列卡片 + 场景色
- 客户成果深色背景 + 引言
- CTA 朱砂红渐变
- 无粒子/水墨动效
- 步骤 5:运行 Lighthouse 审计
运行:npm run lighthouse:mobile
预期:Performance ≥ 90, Accessibility ≥ 95
- 步骤 6:最终 Commit
git add -A
git commit -m "feat: complete Phase 1 of Atlassian-style brand fusion homepage redesign"
自检清单
| 检查项 | 状态 |
|---|---|
| 规格中每个 Section 都有对应任务 | ✅ Hero/SocialProof/ProductMatrix/Challenge/Testimonial/CTA |
| 规格中每个组件都有对应任务 | ✅ MegaDropdown/ProductCard/ChallengeCard/StatsBar/TestimonialBlock/CTASection |
| 无占位符(TODO/TBD/待定) | ✅ 所有步骤包含完整代码 |
| 类型一致性 | ✅ NavigationItemV2/MegaDropdownItem/StatItem 等类型定义明确 |
| 废弃动效清理有对应任务 | ✅ 任务 17 |
| 集成验证有对应任务 | ✅ 任务 18 |