From 829d83522c924c3c29c2d2cdcc56ae1cb0d307de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Tue, 7 Jul 2026 06:53:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(marketing):=20=E9=87=8D=E6=9E=84=E8=90=A5?= =?UTF-8?q?=E9=94=80=E9=A1=B5=E9=9D=A2=E4=B8=8E=E5=9B=9B=E5=B1=82=E5=8F=99?= =?UTF-8?q?=E4=BA=8B=E7=BB=84=E4=BB=B6=E4=BD=93=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构 About、Contact、Team 等静态营销页面 - 重构 News 新闻列表与详情页 - 重构 Products 产品目录、详情与独立产品页面 - 重构 Services 与 Solutions 服务/解决方案页面 - 重构 Detail 四层叙事组件 (Hero → Value → Trust → CTA) - 重构 Sections 页面区块组件 (Hero, CTA, SocialProof, WhyUs 等) - 新增 SectionHeader、ServiceCard、CaseCard 等可复用组件 --- src/app/(marketing)/about/client.tsx | 187 +------ src/app/(marketing)/about/page.test.tsx | 65 ++- src/app/(marketing)/about/page.tsx | 10 +- src/app/(marketing)/contact/page.tsx | 484 +---------------- src/app/(marketing)/layout.tsx | 9 +- .../news/[slug]/NewsDetailClient.tsx | 16 +- src/app/(marketing)/news/[slug]/page.tsx | 43 +- src/app/(marketing)/news/page.tsx | 241 +-------- src/app/(marketing)/page.tsx | 21 +- .../(marketing)/products/[id]/page.test.tsx | 117 ++++- src/app/(marketing)/products/[id]/page.tsx | 79 +-- .../products/erp-upgrade-v3/page.tsx | 84 ++- .../(marketing)/products/erp-upgrade/page.tsx | 67 +-- src/app/(marketing)/products/page.tsx | 238 +-------- .../products/standalone/[id]/client.tsx | 191 +++---- .../products/standalone/[id]/page.tsx | 33 +- src/app/(marketing)/services/[id]/client.tsx | 49 +- src/app/(marketing)/services/[id]/page.tsx | 25 +- src/app/(marketing)/services/page.tsx | 245 +-------- src/app/(marketing)/solutions/[id]/client.tsx | 55 +- src/app/(marketing)/solutions/[id]/page.tsx | 30 +- src/app/(marketing)/solutions/page.tsx | 121 +---- src/app/(marketing)/team/client.tsx | 153 +----- src/app/(marketing)/team/page.tsx | 10 +- src/components/detail/brand-elements.tsx | 188 +++++++ src/components/detail/detail-case-study.tsx | 2 +- .../detail/detail-certification.tsx | 2 +- .../detail/detail-cross-recommend.tsx | 14 +- src/components/detail/detail-cta-section.tsx | 72 ++- src/components/detail/detail-hero.tsx | 236 +++++---- .../detail/detail-product-value.tsx | 444 ++++++++++++++-- .../detail/detail-trust-section.tsx | 171 ++++-- src/components/detail/index.ts | 22 + src/components/detail/list-page-hero.tsx | 166 ++++++ src/components/detail/micro-interactions.tsx | 261 ++++++++++ src/components/detail/product-card.tsx | 86 +++ src/components/detail/service-value.tsx | 189 +++++++ .../detail/solution-service-card.tsx | 162 ++++++ src/components/detail/solution-value.tsx | 221 ++++++++ src/components/sections/case-card.tsx | 130 +++++ src/components/sections/case-detail-page.tsx | 493 ++++++++++++++++++ src/components/sections/challenge-section.tsx | 2 +- src/components/sections/cta-section.tsx | 75 +-- src/components/sections/hero-section-v2.tsx | 30 +- src/components/sections/index.ts | 24 + src/components/sections/industry-grid.tsx | 46 ++ src/components/sections/insight-card.tsx | 91 ++++ .../sections/methodology-section.tsx | 10 +- src/components/sections/product-card.tsx | 83 +++ .../sections/product-matrix-section.tsx | 6 +- src/components/sections/question-card.tsx | 58 +++ src/components/sections/section-header.tsx | 100 ++++ src/components/sections/service-card.tsx | 98 ++++ src/components/sections/service-grid.tsx | 60 +++ .../sections/social-proof-section.tsx | 16 +- src/components/sections/stats-bar.tsx | 131 +++++ src/components/sections/why-us-section.tsx | 30 +- 57 files changed, 3912 insertions(+), 2380 deletions(-) create mode 100644 src/components/detail/brand-elements.tsx create mode 100644 src/components/detail/index.ts create mode 100644 src/components/detail/list-page-hero.tsx create mode 100644 src/components/detail/micro-interactions.tsx create mode 100644 src/components/detail/product-card.tsx create mode 100644 src/components/detail/service-value.tsx create mode 100644 src/components/detail/solution-service-card.tsx create mode 100644 src/components/detail/solution-value.tsx create mode 100644 src/components/sections/case-card.tsx create mode 100644 src/components/sections/case-detail-page.tsx create mode 100644 src/components/sections/index.ts create mode 100644 src/components/sections/industry-grid.tsx create mode 100644 src/components/sections/insight-card.tsx create mode 100644 src/components/sections/product-card.tsx create mode 100644 src/components/sections/question-card.tsx create mode 100644 src/components/sections/section-header.tsx create mode 100644 src/components/sections/service-card.tsx create mode 100644 src/components/sections/service-grid.tsx create mode 100644 src/components/sections/stats-bar.tsx diff --git a/src/app/(marketing)/about/client.tsx b/src/app/(marketing)/about/client.tsx index 427306e..b978f61 100644 --- a/src/app/(marketing)/about/client.tsx +++ b/src/app/(marketing)/about/client.tsx @@ -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 ( -
- {/* Layer 1: Hero */} - - - {/* Brand Story */} -
-
- -
- - 品牌故事 -
-

- 智连未来,成长伙伴 -

- -
- -

智连未来

-

我们坚持对行业趋势的深度研究,不追逐昙花一现的概念。

-

每一次方案,都源于对您业务场景的洞察;

-

每一次连接,都为了让技术真正服务于您的未来。

-
- - -

成长伙伴

-

我们不把“项目完成”当作终点。

-

您的业务增长了吗?您的团队能力提升了吗?

-

您下一次遇到难题时,还会第一个想到我们吗?

-
-
- - {/* Promises */} -
-

我们承诺:

-
- {[ - '不卖您用不上的技术', - '不说不懂业务的术语', - '不做路过就忘的"一锤子买卖"', - ].map((promise) => ( -
- - {promise} -
- ))} -
-

- 我们只做一件事:成为您数字化转型路上,信得过的成长伙伴。 -

-
-
-
-
- - {/* Layer 2: Core Values */} - - - {/* Layer 3: Milestones (Trust) */} - -
-
-
- {MILESTONES.map((milestone, idx) => { - const isLatest = idx === MILESTONES.length - 1; - return ( - -
- {milestone.date} -
-
-
-
-
-
{milestone.date}
-
-

{milestone.title}

- {isLatest && ( - - 进行中 - - )} -
-

{milestone.description}

-
- - ); - })} -
-
- - - {/* Layer 4: CTA */} - -
- ); +export function AboutClient({ data }: { data: Record }) { + return ; } diff --git a/src/app/(marketing)/about/page.test.tsx b/src/app/(marketing)/about/page.test.tsx index 3adf12e..007cae7 100644 --- a/src/app/(marketing)/about/page.test.tsx +++ b/src/app/(marketing)/about/page.test.tsx @@ -54,6 +54,10 @@ jest.mock('lucide-react', () => ({ MapPin: () => , Mail: () => , Phone: () => , + Trophy: () => , + Shield: () => , + Zap: () => , + ArrowUpRight: () => , })); jest.mock('@/components/ui/card', () => { @@ -98,6 +102,23 @@ jest.mock('date-fns', () => ({ subMonths: (d: Date) => d, })); +jest.mock('./about-content-v4', () => { + const MockAboutContentV4 = () => ( +
+

关于我们

+
+

发展历程

+

核心价值观

+

团队组建

+

联系我们

+ 研发产品 +
+
+ ); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + it('should have proper heading hierarchy', async () => { + const pageElement = await AboutPage(); + render(pageElement); const h1 = screen.getByRole('heading', { level: 1 }); expect(h1).toBeInTheDocument(); }); diff --git a/src/app/(marketing)/about/page.tsx b/src/app/(marketing)/about/page.tsx index b308bdf..ca0f6f9 100644 --- a/src/app/(marketing)/about/page.tsx +++ b/src/app/(marketing)/about/page.tsx @@ -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 ; +export default async function AboutPage() { + const items = await getPublishedItems('about-page'); + const data = items[0]?.data ?? {}; + return ; } diff --git a/src/app/(marketing)/contact/page.tsx b/src/app/(marketing)/contact/page.tsx index 302c401..ba04c68 100644 --- a/src/app/(marketing)/contact/page.tsx +++ b/src/app/(marketing)/contact/page.tsx @@ -1,480 +1,8 @@ -'use client'; +import { getPublishedItems } from '@/lib/cms/data-server'; +import ContactContentV3 from './contact-content-v3'; -import { useState, Suspense } from 'react'; -import { useSearchParams } from 'next/navigation'; -import { z } from 'zod'; -import { motion } from 'framer-motion'; -import { useReducedMotion } from '@/hooks/use-reduced-motion'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; -import { Toast } from '@/components/ui/toast'; -import { InkCard } from '@/components/ui/ink-wash'; -import { NarrativeCTA } from '@/components/sections/narrative/NarrativeCTA'; -import { Mail, MapPin, Send, Loader2, Clock, HeadphonesIcon, CheckCircle2, HelpCircle, MessageSquare } from 'lucide-react'; -import { COMPANY_INFO } from '@/lib/constants'; -import { trackContactForm, trackConversion } from '@/lib/analytics'; -import { BreadcrumbSchema } from '@/components/seo/structured-data'; -import { Tooltip } from '@/components/ui/tooltip'; - -const EASE = [0.25, 1, 0.5, 1] as const; - -const contactFormSchema = z.object({ - name: z.string().min(2, '姓名至少需要2个字符'), - phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号码'), - email: z.string().email('请输入有效的邮箱地址'), - subject: z.string().min(2, '主题至少需要2个字符'), - message: z.string().min(10, '留言内容至少需要10个字符'), -}); - -type ContactFormData = z.infer; - -interface FormErrors { - name?: string; - phone?: string; - email?: string; - subject?: string; - message?: string; -} - -const FAQ_ITEMS = [ - { - q: '你们的服务流程是怎样的?', - a: '通常分四步:需求沟通 → 方案评估 → 签约启动 → 敏捷交付。首次咨询免费,我们会在 2 个工作日内给出初步方案建议。', - }, - { - q: '收费模式是怎样的?', - a: '根据项目规模和合作模式灵活定价。项目制按里程碑付款,订阅制按月结算,长期陪跑模式可协商年度合作方案。', - }, - { - q: '数据安全如何保障?', - a: '我们严格遵守数据保护规范,签署保密协议,采用加密传输和权限隔离。项目结束后,客户数据按要求彻底清除。', - }, - { - q: '项目周期一般多长?', - a: '小型项目 2-4 周,中型项目 1-3 个月,大型项目按阶段推进。我们会在方案评估阶段给出明确的时间线。', - }, -]; - -function ContactFormContent() { - const searchParams = useSearchParams(); - const isSuccessFromRedirect = searchParams.get('success') === 'true'; - const shouldReduceMotion = useReducedMotion(); - const [showToast, setShowToast] = useState(isSuccessFromRedirect); - const [toastMessage, setToastMessage] = useState( - isSuccessFromRedirect ? '表单提交成功!我们会尽快与您联系。' : '' - ); - const [toastType, setToastType] = useState<'success' | 'error'>( - isSuccessFromRedirect ? 'success' : 'success' - ); - const [isSubmitting, setIsSubmitting] = useState(false); - const [isSubmitted, setIsSubmitted] = useState(isSuccessFromRedirect); - const [formData, setFormData] = useState({ - name: '', - phone: '', - email: '', - subject: '', - message: '', - }); - const [errors, setErrors] = useState({}); - - const validateField = (field: keyof ContactFormData, value: string) => { - try { - contactFormSchema.shape[field].parse(value); - setErrors((prev) => ({ ...prev, [field]: undefined })); - } catch (error) { - if (error instanceof z.ZodError) { - const fieldError = error.issues[0]; - if (fieldError) { - setErrors((prev) => ({ ...prev, [field]: fieldError.message })); - } - } - } - }; - - const handleChange = (field: keyof ContactFormData, value: string) => { - setFormData((prev) => ({ ...prev, [field]: value })); - if (errors[field]) { - validateField(field, value); - } - }; - - const handleBlur = (field: keyof ContactFormData, value: string) => { - validateField(field, value); - }; - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - - const result = contactFormSchema.safeParse(formData); - - if (!result.success) { - const fieldErrors: FormErrors = {}; - result.error.issues.forEach((issue) => { - const field = issue.path[0] as keyof ContactFormData; - fieldErrors[field] = issue.message; - }); - setErrors(fieldErrors); - return; - } - - setIsSubmitting(true); - - try { - const formBody = new URLSearchParams(); - formBody.append('name', formData.name); - formBody.append('phone', formData.phone); - formBody.append('email', formData.email); - formBody.append('subject', formData.subject); - formBody.append('message', formData.message); - - const response = await fetch('/api/contact', { - method: 'POST', - body: formBody, - }); - - const data = await response.json(); - - if (response.ok && (data.success === 'true' || data.success === true)) { - trackContactForm({ - name: formData.name, - email: formData.email, - company: formData.subject, - }); - trackConversion('contact_form_submission'); - setToastMessage('表单提交成功!我们会尽快与您联系。'); - setToastType('success'); - setShowToast(true); - setIsSubmitted(true); - setFormData({ name: '', phone: '', email: '', subject: '', message: '' }); - setErrors({}); - } else { - const errorMsg = data.message || '提交失败,请稍后重试或直接发送邮件联系我们。'; - if (errorMsg.includes('HTML files') || errorMsg.includes('web server')) { - setToastMessage('表单服务需要在生产环境激活。部署后首次提交会发送确认邮件到 ' + COMPANY_INFO.email); - } else { - setToastMessage(errorMsg); - } - setToastType('error'); - setShowToast(true); - } - } catch { - setToastMessage('网络错误,请稍后重试。'); - setToastType('error'); - setShowToast(true); - } finally { - setIsSubmitting(false); - } - } - - const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } }; - - return ( -
- - {showToast && ( - setShowToast(false)} - /> - )} - - {/* Hero */} -
-
- -
- 联系我们 -
-

- 工作日 2 小时内快速响应,首次咨询免费 -

-

- 随时欢迎{' '} - 聊一聊 -

-

- 无论是一个明确的项目需求,还是一个模糊的想法,我们都愿意坐下来和您一起理清楚。没有任何销售压力。 -

-
-
-
- - {/* Form + Info */} -
-
-
- {/* Left: Contact Info */} - -
-

联系方式

-
-
-
- -
- -
- -
-
- -
-
-

地址

-

{COMPANY_INFO.address}

-
-
-
-
- - -
- -

工作时间

-
-
- 周一至周五 - 9:00 - 18:00 -
-
- - -
- -

我们的承诺

-
-
-
-
-

工作日 2 小时内快速响应您的咨询

-
-
-
-

提供免费的业务咨询和方案评估服务

-
-
-
-

根据您的需求量身定制最优解决方案

-
-
- - - - {/* Right: Form */} - - -

发送消息

- - {isSubmitted ? ( -
-
- -
-

消息已发送

-

感谢您的留言,我们会尽快与您联系!

-
- ) : ( -
- -
-
- - 姓名 - - - - - } - id="name" - placeholder="请输入您的姓名" - required - value={formData.name} - onChange={(e) => handleChange('name', e.target.value)} - onBlur={(e) => handleBlur('name', e.target.value)} - error={errors.name} - /> -
-
- - 电话 - - - - - } - id="phone" - type="tel" - placeholder="请输入11位手机号" - required - value={formData.phone} - onChange={(e) => handleChange('phone', e.target.value)} - onBlur={(e) => handleBlur('phone', e.target.value)} - error={errors.phone} - /> -
-
- handleChange('email', e.target.value)} - onBlur={(e) => handleBlur('email', e.target.value)} - error={errors.email} - /> - handleChange('subject', e.target.value)} - onBlur={(e) => handleBlur('subject', e.target.value)} - error={errors.subject} - /> -