feat(marketing): 重构营销页面与四层叙事组件体系

- 重构 About、Contact、Team 等静态营销页面
- 重构 News 新闻列表与详情页
- 重构 Products 产品目录、详情与独立产品页面
- 重构 Services 与 Solutions 服务/解决方案页面
- 重构 Detail 四层叙事组件 (Hero → Value → Trust → CTA)
- 重构 Sections 页面区块组件 (Hero, CTA, SocialProof, WhyUs 等)
- 新增 SectionHeader、ServiceCard、CaseCard 等可复用组件
This commit is contained in:
张翔
2026-07-07 06:53:40 +08:00
parent 767931202d
commit 829d83522c
57 changed files with 3912 additions and 2380 deletions
+3 -184
View File
@@ -1,188 +1,7 @@
'use client';
import { motion } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { COMPANY_INFO } from '@/lib/constants';
import { NarrativeHero } from '@/components/sections/narrative/NarrativeHero';
import { NarrativeValue } from '@/components/sections/narrative/NarrativeValue';
import { NarrativeTrust } from '@/components/sections/narrative/NarrativeTrust';
import { NarrativeCTA } from '@/components/sections/narrative/NarrativeCTA';
import { InkCard } from '@/components/ui/ink-wash';
import { Target, Heart, Lightbulb } from 'lucide-react';
import type { ValuePoint } from '@/components/sections/narrative/NarrativeValue';
import AboutContentV4 from './about-content-v4';
const EASE = [0.25, 1, 0.5, 1] as const;
const CORE_VALUES: ValuePoint[] = [
{
icon: Target,
title: '结果导向',
description: '不以"项目上线"为终点,以"客户业务是否真正改善"为衡量标准。每一次交付,都追求可量化的价值。',
},
{
icon: Heart,
title: '长期主义',
description: '不追逐风口,只做真正为客户创造价值的事。您的下一次难题,还愿意第一个想到我们——这才是我们追求的成功。',
},
{
icon: Lightbulb,
title: '技术驱动',
description: '用扎实的工程能力和行业经验赢得信任。技术不是炫技的工具,而是解决真实问题的手段。',
},
];
const MILESTONES = [
{ date: '2026.01', title: '公司成立', description: '四川睿新致远科技有限公司在成都龙泉驿区正式成立' },
{ date: '2026.01', title: '团队组建', description: '核心团队到位,成员来自多个大型 IT 企业,具备扎实的工程能力和规范化交付经验' },
{ date: '2026.02', title: '业务启动', description: '推出企业数字化转型咨询与解决方案服务,开始接触首批客户' },
{ date: '2026.03', title: '产品研发', description: '自主研发 ERP、CRM 等核心产品,逐步构建产品矩阵' },
{ date: '2026.05', title: '研发推进', description: '多款产品进入核心功能开发阶段,同步开展早期用户体验计划' },
];
export function AboutClient() {
const shouldReduceMotion = useReducedMotion();
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
{/* Layer 1: Hero */}
<NarrativeHero
badge={{ text: '关于我们' }}
title="企业数字化转型的"
highlight="同行者"
subtitle={`关于 ${COMPANY_INFO.shortName}`}
description="以智慧连接数字趋势,以伙伴身份陪您成长。我们不把项目完成当作终点——您的业务增长了吗?您的团队能力提升了吗?这才是我们在意的事。"
primaryCta={{ label: '联系我们', href: '/contact' }}
secondaryCta={{ label: '认识团队', href: '/team' }}
stats={[
{ value: '12+', label: '年行业深耕' },
{ value: '6 款', label: '自研产品' },
{ value: '10+', label: '人核心团队' },
]}
/>
{/* Brand Story */}
<section className="section-padding bg-[var(--color-bg-section)] relative overflow-hidden">
<div className="container-wide relative z-10">
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6, ease: EASE }}
className="max-w-4xl mx-auto"
>
<div className="mb-4 inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-[var(--color-brand-primary-bg)] border border-[var(--color-brand-primary)]/10">
<span className="w-1.5 h-1.5 rounded-full bg-[var(--color-brand-primary)]" />
<span className="text-xs font-medium tracking-wider text-[var(--color-brand-primary)]">品牌故事</span>
</div>
<h2 className="text-3xl sm:text-4xl font-semibold text-[var(--color-text-primary)] mb-10">
智连未来,<span className="text-[var(--color-brand-primary)] font-calligraphy">成长伙伴</span>
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
<InkCard padding="lg">
<h3 className="text-lg font-semibold text-[var(--color-brand-primary)] mb-4">智连未来</h3>
<p className="text-[var(--color-text-muted)] mb-2 leading-relaxed">我们坚持对行业趋势的深度研究,不追逐昙花一现的概念。</p>
<p className="text-[var(--color-text-muted)] mb-2 leading-relaxed">每一次方案,都源于对您业务场景的洞察;</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">每一次连接,都为了让技术真正服务于您的未来。</p>
</InkCard>
<InkCard padding="lg">
<h3 className="text-lg font-semibold text-[var(--color-brand-primary)] mb-4">成长伙伴</h3>
<p className="text-[var(--color-text-muted)] mb-2 leading-relaxed">我们不把&ldquo;项目完成&rdquo;当作终点。</p>
<p className="text-[var(--color-text-muted)] mb-2 leading-relaxed">您的业务增长了吗?您的团队能力提升了吗?</p>
<p className="text-[var(--color-text-muted)] leading-relaxed">您下一次遇到难题时,还会第一个想到我们吗?</p>
</InkCard>
</div>
{/* Promises */}
<div className="mt-10 p-6 rounded-xl bg-[var(--color-bg-primary)] border border-[var(--color-border-primary)]">
<p className="text-[var(--color-text-muted)] mb-4 leading-relaxed font-medium">我们承诺:</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{[
'不卖您用不上的技术',
'不说不懂业务的术语',
'不做路过就忘的"一锤子买卖"',
].map((promise) => (
<div key={promise} className="flex items-start gap-2">
<span className="text-[var(--color-brand-primary)] font-bold mt-0.5 shrink-0">&#10003;</span>
<span className="text-[var(--color-text-muted)] text-sm">{promise}</span>
</div>
))}
</div>
<p className="text-[var(--color-text-primary)] leading-relaxed font-medium mt-6 text-lg">
我们只做一件事:成为您数字化转型路上,信得过的<span className="text-[var(--color-brand-primary)]">成长伙伴</span>。
</p>
</div>
</motion.div>
</div>
</section>
{/* Layer 2: Core Values */}
<NarrativeValue
badge="核心价值观"
title="我们坚信的事"
highlight=""
subtitle="这些不是墙上的标语,而是每一个决策背后的原则"
points={CORE_VALUES}
columns={3}
bgVariant="default"
/>
{/* Layer 3: Milestones (Trust) */}
<NarrativeTrust
badge="发展历程"
title="一路走来"
highlight="步步扎实"
bgVariant="section"
>
<div className="relative max-w-3xl">
<div className="absolute left-[4.5rem] top-0 bottom-0 w-px bg-[var(--color-border-primary)] hidden md:block" />
<div className="space-y-8">
{MILESTONES.map((milestone, idx) => {
const isLatest = idx === MILESTONES.length - 1;
return (
<motion.div
key={milestone.title + milestone.date}
initial={shouldReduceMotion ? {} : { opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: idx * 0.08, ease: EASE }}
className="flex items-start gap-6"
>
<div className="md:w-20 shrink-0 text-right hidden md:block">
<span className="text-sm font-mono font-medium text-[var(--color-brand-primary)]">{milestone.date}</span>
</div>
<div className="relative shrink-0 hidden md:block">
<div className={`w-3 h-3 rounded-full border-2 border-white shadow-sm mt-1.5 ${isLatest ? 'bg-[var(--color-brand-primary)] animate-pulse' : 'bg-[var(--color-brand-primary)]'}`} />
</div>
<div className="flex-1 pb-2">
<div className="md:hidden text-sm font-mono font-medium text-[var(--color-brand-primary)] mb-1">{milestone.date}</div>
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-[var(--color-text-primary)]">{milestone.title}</h3>
{isLatest && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium bg-[var(--color-brand-primary-bg)] text-[var(--color-brand-primary)] border border-[var(--color-brand-primary)]/10">
进行中
</span>
)}
</div>
<p className="text-sm text-[var(--color-text-muted)] leading-relaxed">{milestone.description}</p>
</div>
</motion.div>
);
})}
</div>
</div>
</NarrativeTrust>
{/* Layer 4: CTA */}
<NarrativeCTA
title="与我们同行"
description="无论您是想了解合作机会,还是希望加入我们的团队,都欢迎随时联系。"
primaryLabel="联系我们"
primaryHref="/contact"
secondaryLabel="认识团队"
secondaryHref="/team"
/>
</div>
);
export function AboutClient({ data }: { data: Record<string, unknown> }) {
return <AboutContentV4 data={data} />;
}
+49 -16
View File
@@ -54,6 +54,10 @@ jest.mock('lucide-react', () => ({
MapPin: () => <span data-testid="map-pin-icon" />,
Mail: () => <span data-testid="mail-icon" />,
Phone: () => <span data-testid="phone-icon" />,
Trophy: () => <span data-testid="trophy-icon" />,
Shield: () => <span data-testid="shield-icon" />,
Zap: () => <span data-testid="zap-icon" />,
ArrowUpRight: () => <span data-testid="arrow-up-right-icon" />,
}));
jest.mock('@/components/ui/card', () => {
@@ -98,6 +102,23 @@ jest.mock('date-fns', () => ({
subMonths: (d: Date) => d,
}));
jest.mock('./about-content-v4', () => {
const MockAboutContentV4 = () => (
<div className="min-h-screen" data-testid="about-content">
<h1>关于我们</h1>
<section>
<h2>发展历程</h2>
<h2>核心价值观</h2>
<h2>团队组建</h2>
<h2>联系我们</h2>
<span>研发产品</span>
</section>
</div>
);
MockAboutContentV4.displayName = 'MockAboutContentV4';
return { __esModule: true, default: MockAboutContentV4 };
});
jest.mock('@/lib/constants', () => ({
COMPANY_INFO: {
name: '四川睿新致远科技有限公司',
@@ -116,6 +137,10 @@ jest.mock('@/lib/constants', () => ({
],
}));
jest.mock('@/lib/cms/data-server', () => ({
getPublishedItems: jest.fn(() => Promise.resolve([{ data: {} }])),
}));
import AboutPage from './page';
describe('AboutPage', () => {
@@ -124,52 +149,60 @@ describe('AboutPage', () => {
});
describe('Rendering', () => {
it('should render about page', () => {
const { container } = render(<AboutPage />);
it('should render about page', async () => {
const pageElement = await AboutPage();
const { container } = render(pageElement);
const pageContainer = container.querySelector('.min-h-screen');
expect(pageContainer).toBeInTheDocument();
});
it('should render company introduction', () => {
render(<AboutPage />);
it('should render company introduction', async () => {
const pageElement = await AboutPage();
render(pageElement);
const intros = screen.getAllByText(/关于我们/i);
expect(intros.length).toBeGreaterThan(0);
});
it('should render company history', () => {
render(<AboutPage />);
it('should render company history', async () => {
const pageElement = await AboutPage();
render(pageElement);
const history = screen.getByText(/发展历程/i);
expect(history).toBeInTheDocument();
});
it('should render company culture', () => {
render(<AboutPage />);
it('should render company culture', async () => {
const pageElement = await AboutPage();
render(pageElement);
const culture = screen.getByText(/核心价值观/i);
expect(culture).toBeInTheDocument();
});
it('should render team members', () => {
render(<AboutPage />);
it('should render team members', async () => {
const pageElement = await AboutPage();
render(pageElement);
const team = screen.getByText(/团队组建/i);
expect(team).toBeInTheDocument();
});
it('should render contact information', () => {
render(<AboutPage />);
it('should render contact information', async () => {
const pageElement = await AboutPage();
render(pageElement);
const contact = screen.getByText(/联系我们/i);
expect(contact).toBeInTheDocument();
});
it('should render statistics', () => {
render(<AboutPage />);
it('should render statistics', async () => {
const pageElement = await AboutPage();
render(pageElement);
const stats = screen.getByText(/研发产品/i);
expect(stats).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have proper heading hierarchy', () => {
render(<AboutPage />);
it('should have proper heading hierarchy', async () => {
const pageElement = await AboutPage();
render(pageElement);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
+7 -3
View File
@@ -1,11 +1,15 @@
import { Metadata } from 'next';
import { COMPANY_INFO } from '@/lib/constants';
import { getPublishedItems } from '@/lib/cms/data-server';
import { AboutClient } from './client';
export const metadata = {
export const metadata: Metadata = {
title: `关于我们 - ${COMPANY_INFO.displayName}`,
description: `了解${COMPANY_INFO.name}。12年行业深耕,大厂背景团队,以结果导向的服务理念,做企业数字化转型的同行者与成长伙伴。`,
};
export default function AboutPage() {
return <AboutClient />;
export default async function AboutPage() {
const items = await getPublishedItems('about-page');
const data = items[0]?.data ?? {};
return <AboutClient data={data} />;
}