Files
novalon-website/docs/superpowers/plans/2026-04-24-erp-product-landing-page.md
T
张翔 fe6e4b1c54 refactor: P0 - remove testimonial, migrate footer & mobile menu to NAVIGATION_V2
- Remove TestimonialSection from homepage (no customers yet)
- Footer: dark theme, NAVIGATION_V2 + MEGA_DROPDOWN_DATA links
- Mobile Menu: NAVIGATION_V2 with collapsible dropdown support
2026-04-30 22:00:00 +08:00

35 KiB
Raw Blame History

ERP 产品落地页实现计划

面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(- [ ])语法来跟踪进度。

目标: 将 ERP 产品详情页改造为 Terminal 风格的全屏沉浸式落地页

架构: 组件化重构,每个 section 独立组件,复用现有 Framer Motion 动画库,通过客户端组件组合所有 section

技术栈: Next.js 16 + React 19 + Tailwind CSS v4 + Framer Motion


文件结构

新建文件:
src/components/products/
├── product-hero-section.tsx        # Section 1: 全屏沉浸 Hero
├── product-overview-section.tsx    # Section 2: 产品概述
├── product-features-section.tsx    # Section 3: 核心功能(滚动叙事)
├── product-benefits-section.tsx    # Section 4: 产品优势
├── product-process-section.tsx     # Section 5: 实施流程
├── product-specs-section.tsx       # Section 6: 技术规格
├── product-pricing-section.tsx     # Section 7: 定价方案
├── product-cta-section.tsx         # Section 8: CTA 横幅
└── index.ts                        # 统一导出

src/app/(marketing)/products/[id]/
└── product-detail-client.tsx       # 客户端组件入口

修改文件:
src/app/(marketing)/products/[id]/page.tsx
src/components/layout/header.tsx

任务 1:创建产品 Hero Section 组件

文件:

  • 创建:src/components/products/product-hero-section.tsx

  • 步骤 1:创建 product-hero-section.tsx

'use client';

import { useEffect, useRef, useState } from 'react';
import { motion, useScroll, useTransform } from 'framer-motion';
import dynamic from 'next/dynamic';
import { MagneticButton } from '@/lib/animations';
import { ChevronDown } from 'lucide-react';
import type { Product } from '@/lib/constants/products';

const InkBackground = dynamic(
  () => import('@/components/ui/ink-decoration').then(mod => ({ default: mod.InkBackground })),
  { ssr: false }
);

const DataParticleFlow = dynamic(
  () => import('@/components/effects/data-particle-flow').then(mod => ({ default: mod.DataParticleFlow })),
  { ssr: false }
);

interface ProductHeroSectionProps {
  product: Product;
}

export function ProductHeroSection({ product }: ProductHeroSectionProps) {
  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
      ref={sectionRef}
      className="relative min-h-screen flex items-center justify-center overflow-hidden bg-[#0A0A0A]"
    >
      {/* 背景特效 */}
      <InkBackground />
      <DataParticleFlow
        particleCount={80}
        color="#C41E3A"
        intensity="subtle"
        shape="square"
        effect="pulse"
      />

      {/* 内容 */}
      <div className="container-wide relative z-10 py-24 md:py-32">
        <div className="max-w-4xl mx-auto text-center">
          {/* 分类标签 */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={isVisible ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.1 }}
            className="inline-block px-4 py-2 bg-[#C41E3A]/20 rounded-full text-[#C41E3A] text-sm mb-6"
          >
            {product.category}
          </motion.div>

          {/* 产品名称 */}
          <motion.h1
            initial={{ opacity: 0, y: 30 }}
            animate={isVisible ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.8, delay: 0.2 }}
            className="text-4xl md:text-6xl lg:text-7xl font-bold text-white mb-6"
          >
            {product.title}
          </motion.h1>

          {/* 价值主张 */}
          <motion.p
            initial={{ opacity: 0, y: 20 }}
            animate={isVisible ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.4 }}
            className="text-lg md:text-xl text-[#B0B0B0] leading-relaxed mb-10 max-w-2xl mx-auto"
          >
            {product.description}
          </motion.p>

          {/* CTA 按钮 */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={isVisible ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.6 }}
          >
            <MagneticButton
              href="/contact"
              className="bg-[#C41E3A] hover:bg-[#A01830] text-white px-8 py-4 rounded-lg text-lg font-semibold"
            >
              预约演示
            </MagneticButton>
          </motion.div>
        </div>
      </div>

      {/* 滚动指示器 */}
      <motion.div
        initial={{ opacity: 0 }}
        animate={isVisible ? { opacity: 1 } : {}}
        transition={{ duration: 0.6, delay: 1 }}
        className="absolute bottom-8 left-1/2 -translate-x-1/2"
      >
        <motion.div
          animate={{ y: [0, 10, 0] }}
          transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
          className="text-white/60"
        >
          <ChevronDown className="w-8 h-8" />
        </motion.div>
      </motion.div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-hero-section.tsx
git commit -m "feat(products): add product hero section component"

任务 2:创建产品概述 Section 组件

文件:

  • 创建:src/components/products/product-overview-section.tsx

  • 步骤 1:创建 product-overview-section.tsx

'use client';

import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { TextReveal } from '@/components/ui/scroll-animations';
import type { Product } from '@/lib/constants/products';

interface ProductOverviewSectionProps {
  product: Product;
}

export function ProductOverviewSection({ product }: ProductOverviewSectionProps) {
  const ref = useRef<HTMLElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <section
      ref={ref}
      className="relative py-24 md:py-32 bg-white overflow-hidden"
    >
      <div className="container-wide">
        <div className="max-w-4xl mx-auto">
          {/* 标题 */}
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6 }}
            className="text-3xl md:text-4xl font-bold text-[#1C1C1C] mb-8 text-center"
          >
            产品概述
          </motion.h2>

          {/* 概述文字 - 逐词揭示 */}
          <TextReveal
            text={product.overview}
            className="text-lg md:text-xl text-[#5C5C5C] leading-relaxed text-center"
          />
        </div>
      </div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-overview-section.tsx
git commit -m "feat(products): add product overview section component"

任务 3:创建核心功能 Section 组件(滚动叙事)

文件:

  • 创建:src/components/products/product-features-section.tsx

  • 步骤 1:创建 product-features-section.tsx

'use client';

import { motion, useInView, useScroll, useTransform } from 'framer-motion';
import { useRef } from 'react';
import type { Product } from '@/lib/constants/products';

interface ProductFeaturesSectionProps {
  product: Product;
}

function FeatureItem({
  feature,
  index,
}: {
  feature: string;
  index: number;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-50px' });

  // 解析功能标题和描述
  const [title, ...descParts] = feature.split('');
  const description = descParts.join('');

  // 编号格式化
  const number = String(index + 1).padStart(2, '0');

  return (
    <div
      ref={ref}
      className="min-h-[60vh] flex items-center py-16"
    >
      <div className="container-wide">
        <div className="max-w-6xl mx-auto grid md:grid-cols-2 gap-12 items-center">
          {/* 左侧:编号和文字 */}
          <div className="order-2 md:order-1">
            {/* 编号 */}
            <motion.span
              initial={{ opacity: 0, x: -30 }}
              animate={isInView ? { opacity: 1, x: 0 } : {}}
              transition={{ duration: 0.6 }}
              className="block text-7xl md:text-8xl font-mono text-[#C41E3A]/20 mb-4"
            >
              {number}
            </motion.span>

            {/* 功能标题 */}
            <motion.h3
              initial={{ opacity: 0, y: 20 }}
              animate={isInView ? { opacity: 1, y: 0 } : {}}
              transition={{ duration: 0.6, delay: 0.1 }}
              className="text-2xl md:text-3xl font-bold text-white mb-4"
            >
              {title}
            </motion.h3>

            {/* 功能描述 */}
            {description && (
              <motion.p
                initial={{ opacity: 0, y: 20 }}
                animate={isInView ? { opacity: 1, y: 0 } : {}}
                transition={{ duration: 0.6, delay: 0.2 }}
                className="text-lg text-[#B0B0B0] leading-relaxed"
              >
                {description}
              </motion.p>
            )}
          </div>

          {/* 右侧:动画占位区域 */}
          <motion.div
            initial={{ opacity: 0, scale: 0.9 }}
            animate={isInView ? { opacity: 1, scale: 1 } : {}}
            transition={{ duration: 0.8, delay: 0.3 }}
            className="order-1 md:order-2 aspect-square rounded-2xl bg-gradient-to-br from-[#1A1A1A] to-[#0A0A0A] border border-[#2A2A2A] flex items-center justify-center"
          >
            <div className="w-24 h-24 rounded-full bg-[#C41E3A]/20 flex items-center justify-center">
              <div className="w-12 h-12 rounded-full bg-[#C41E3A]/40" />
            </div>
          </motion.div>
        </div>
      </div>
    </div>
  );
}

export function ProductFeaturesSection({ product }: ProductFeaturesSectionProps) {
  const sectionRef = useRef<HTMLElement>(null);

  return (
    <section
      ref={sectionRef}
      className="relative bg-[#0A0A0A] overflow-hidden"
    >
      {/* 标题 */}
      <div className="pt-24 md:pt-32 pb-8">
        <div className="container-wide">
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6 }}
            className="text-3xl md:text-4xl font-bold text-white text-center mb-4"
          >
            核心功能
          </motion.h2>
          <motion.p
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.6, delay: 0.1 }}
            className="text-lg text-[#B0B0B0] text-center max-w-2xl mx-auto"
          >
            全方位覆盖企业核心业务场景
          </motion.p>
        </div>
      </div>

      {/* 功能列表 */}
      {product.features.map((feature, index) => (
        <FeatureItem key={index} feature={feature} index={index} />
      ))}
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-features-section.tsx
git commit -m "feat(products): add product features section with scroll storytelling"

任务 4:创建产品优势 Section 组件

文件:

  • 创建:src/components/products/product-benefits-section.tsx

  • 步骤 1:创建 product-benefits-section.tsx

'use client';

import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { StaggerReveal, StaggerItem, CounterWithEffect } from '@/lib/animations';
import type { Product } from '@/lib/constants/products';

interface ProductBenefitsSectionProps {
  product: Product;
}

// 解析优势中的数字
function extractNumber(text: string): { number: number; suffix: string } | null {
  const match = text.match(/(\d+)%/);
  if (match) {
    return { number: parseInt(match[1], 10), suffix: '%' };
  }
  return null;
}

function BenefitCard({
  benefit,
  index,
}: {
  benefit: string;
  index: number;
}) {
  const numberInfo = extractNumber(benefit);

  return (
    <StaggerItem className="p-6 md:p-8 bg-[#F5F7FA] rounded-2xl hover:bg-[#FFFBF5] transition-colors">
      {numberInfo && (
        <div className="mb-4">
          <span className="text-4xl md:text-5xl font-bold text-[#C41E3A]">
            <CounterWithEffect end={numberInfo.number} suffix={numberInfo.suffix} />
          </span>
        </div>
      )}
      <p className="text-lg text-[#1C1C1C] leading-relaxed">
        {benefit}
      </p>
    </StaggerItem>
  );
}

export function ProductBenefitsSection({ product }: ProductBenefitsSectionProps) {
  const ref = useRef<HTMLElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <section
      ref={ref}
      className="relative py-24 md:py-32 bg-white overflow-hidden"
    >
      <div className="container-wide">
        {/* 标题 */}
        <motion.h2
          initial={{ opacity: 0, y: 20 }}
          animate={isInView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6 }}
          className="text-3xl md:text-4xl font-bold text-[#1C1C1C] mb-4 text-center"
        >
          产品优势
        </motion.h2>

        <motion.p
          initial={{ opacity: 0, y: 20 }}
          animate={isInView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6, delay: 0.1 }}
          className="text-lg text-[#5C5C5C] text-center mb-12 max-w-2xl mx-auto"
        >
          经过验证的业务价值提升
        </motion.p>

        {/* 优势卡片网格 */}
        <StaggerReveal className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
          {product.benefits.map((benefit, index) => (
            <BenefitCard key={index} benefit={benefit} index={index} />
          ))}
        </StaggerReveal>
      </div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-benefits-section.tsx
git commit -m "feat(products): add product benefits section with counter animation"

任务 5:创建实施流程 Section 组件

文件:

  • 创建:src/components/products/product-process-section.tsx

  • 步骤 1:创建 product-process-section.tsx

'use client';

import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import type { Product } from '@/lib/constants/products';

interface ProductProcessSectionProps {
  product: Product;
}

function ProcessStep({
  step,
  index,
  total,
}: {
  step: string;
  index: number;
  total: number;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-50px' });

  // 解析步骤标题和描述
  const [title, ...descParts] = step.split('');
  const description = descParts.join('');

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, x: -30 }}
      animate={isInView ? { opacity: 1, x: 0 } : {}}
      transition={{ duration: 0.6, delay: index * 0.1 }}
      className="flex items-start gap-6"
    >
      {/* 编号圆圈 */}
      <div className="flex-shrink-0">
        <div className="w-12 h-12 rounded-full bg-[#C41E3A] flex items-center justify-center text-white font-bold text-lg">
          {index + 1}
        </div>
        {index < total - 1 && (
          <div className="w-0.5 h-16 bg-[#C41E3A]/20 ml-6 mt-2" />
        )}
      </div>

      {/* 内容 */}
      <div className="flex-1 pb-8">
        <h3 className="text-xl font-semibold text-[#1C1C1C] mb-2">
          {title}
        </h3>
        {description && (
          <p className="text-[#5C5C5C] leading-relaxed">
            {description}
          </p>
        )}
      </div>
    </motion.div>
  );
}

export function ProductProcessSection({ product }: ProductProcessSectionProps) {
  const ref = useRef<HTMLElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <section
      ref={ref}
      className="relative py-24 md:py-32 bg-[#F5F5F5] overflow-hidden"
    >
      <div className="container-wide">
        <div className="max-w-3xl mx-auto">
          {/* 标题 */}
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6 }}
            className="text-3xl md:text-4xl font-bold text-[#1C1C1C] mb-4 text-center"
          >
            实施流程
          </motion.h2>

          <motion.p
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.1 }}
            className="text-lg text-[#5C5C5C] text-center mb-12"
          >
            专业团队全程护航,确保项目成功落地
          </motion.p>

          {/* 步骤列表 */}
          <div className="mt-8">
            {product.process.map((step, index) => (
              <ProcessStep
                key={index}
                step={step}
                index={index}
                total={product.process.length}
              />
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-process-section.tsx
git commit -m "feat(products): add product process section with timeline"

任务 6:创建技术规格 Section 组件

文件:

  • 创建:src/components/products/product-specs-section.tsx

  • 步骤 1:创建 product-specs-section.tsx

'use client';

import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { StaggerReveal, StaggerItem } from '@/components/ui/scroll-animations';
import type { Product } from '@/lib/constants/products';

interface ProductSpecsSectionProps {
  product: Product;
}

export function ProductSpecsSection({ product }: ProductSpecsSectionProps) {
  const ref = useRef<HTMLElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <section
      ref={ref}
      className="relative py-24 md:py-32 bg-white overflow-hidden"
    >
      <div className="container-wide">
        {/* 标题 */}
        <motion.h2
          initial={{ opacity: 0, y: 20 }}
          animate={isInView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6 }}
          className="text-3xl md:text-4xl font-bold text-[#1C1C1C] mb-4 text-center"
        >
          技术规格
        </motion.h2>

        <motion.p
          initial={{ opacity: 0, y: 20 }}
          animate={isInView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6, delay: 0.1 }}
          className="text-lg text-[#5C5C5C] text-center mb-12 max-w-2xl mx-auto"
        >
          灵活配置,满足多样化业务需求
        </motion.p>

        {/* 规格网格 */}
        <StaggerReveal className="grid md:grid-cols-2 gap-4 max-w-3xl mx-auto">
          {product.specs.map((spec, index) => (
            <StaggerItem
              key={index}
              className="flex items-center gap-3 p-4 bg-[#F5F7FA] rounded-lg"
            >
              <div className="w-2 h-2 bg-[#C41E3A] rounded-full flex-shrink-0" />
              <span className="text-[#1C1C1C]">{spec}</span>
            </StaggerItem>
          ))}
        </StaggerReveal>
      </div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-specs-section.tsx
git commit -m "feat(products): add product specs section"

任务 7:创建定价方案 Section 组件

文件:

  • 创建:src/components/products/product-pricing-section.tsx

  • 步骤 1:创建 product-pricing-section.tsx

'use client';

import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { StaggerReveal, StaggerItem } from '@/components/ui/scroll-animations';
import { MagneticButton } from '@/lib/animations';
import { Check } from 'lucide-react';
import type { Product } from '@/lib/constants/products';

interface ProductPricingSectionProps {
  product: Product;
}

const pricingFeatures = {
  base: ['基础功能模块', '邮件支持', '标准报表'],
  standard: ['全部功能模块', '电话支持', '自定义报表', '优先响应'],
  enterprise: ['全部功能模块', '专属客服', '定制开发', 'SLA保障'],
};

function PricingCard({
  name,
  price,
  features,
  isRecommended = false,
}: {
  name: string;
  price: string;
  features: string[];
  isRecommended?: boolean;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-50px' });

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 30 }}
      animate={isInView ? { opacity: 1, y: 0 } : {}}
      transition={{ duration: 0.6 }}
      className={`
        relative p-6 md:p-8 rounded-2xl
        ${isRecommended
          ? 'bg-gradient-to-br from-[#C41E3A] to-[#A01830] text-white'
          : 'bg-[#1A1A1A] border border-[#2A2A2A]'
        }
      `}
    >
      {/* 推荐标签 */}
      {isRecommended && (
        <div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-white text-[#C41E3A] px-4 py-1 rounded-full text-sm font-semibold">
          推荐
        </div>
      )}

      {/* 名称 */}
      <h3 className={`text-xl font-semibold mb-2 ${isRecommended ? 'text-white' : 'text-white'}`}>
        {name}
      </h3>

      {/* 价格 */}
      <p className={`text-3xl font-bold mb-6 ${isRecommended ? 'text-white' : 'text-[#C41E3A]'}`}>
        {price}
      </p>

      {/* 功能列表 */}
      <ul className="space-y-3 mb-8">
        {features.map((feature, index) => (
          <li key={index} className="flex items-center gap-2">
            <Check className={`w-5 h-5 ${isRecommended ? 'text-white' : 'text-[#C41E3A]'}`} />
            <span className={isRecommended ? 'text-white/90' : 'text-[#B0B0B0]'}>
              {feature}
            </span>
          </li>
        ))}
      </ul>

      {/* 按钮 */}
      <MagneticButton
        href="/contact"
        className={`
          w-full py-3 rounded-lg font-semibold text-center
          ${isRecommended
            ? 'bg-white text-[#C41E3A] hover:bg-white/90'
            : 'bg-[#C41E3A] text-white hover:bg-[#A01830]'
          }
        `}
      >
        立即咨询
      </MagneticButton>
    </motion.div>
  );
}

export function ProductPricingSection({ product }: ProductPricingSectionProps) {
  const ref = useRef<HTMLElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <section
      ref={ref}
      className="relative py-24 md:py-32 bg-[#0A0A0A] overflow-hidden"
    >
      <div className="container-wide">
        {/* 标题 */}
        <motion.h2
          initial={{ opacity: 0, y: 20 }}
          animate={isInView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6 }}
          className="text-3xl md:text-4xl font-bold text-white mb-4 text-center"
        >
          价格方案
        </motion.h2>

        <motion.p
          initial={{ opacity: 0, y: 20 }}
          animate={isInView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.6, delay: 0.1 }}
          className="text-lg text-[#B0B0B0] text-center mb-12 max-w-2xl mx-auto"
        >
          灵活定价,按需选择
        </motion.p>

        {/* 定价卡片 */}
        <div className="grid md:grid-cols-3 gap-6 max-w-5xl mx-auto">
          <PricingCard
            name="基础版"
            price={product.pricing.base}
            features={pricingFeatures.base}
          />
          <PricingCard
            name="标准版"
            price={product.pricing.standard}
            features={pricingFeatures.standard}
            isRecommended
          />
          <PricingCard
            name="企业版"
            price={product.pricing.enterprise}
            features={pricingFeatures.enterprise}
          />
        </div>
      </div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-pricing-section.tsx
git commit -m "feat(products): add product pricing section with dark theme"

任务 8:创建 CTA Section 组件

文件:

  • 创建:src/components/products/product-cta-section.tsx

  • 步骤 1:创建 product-cta-section.tsx

'use client';

import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { MagneticButton } from '@/lib/animations';

export function ProductCTASection() {
  const ref = useRef<HTMLElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-100px' });

  return (
    <section
      ref={ref}
      className="relative py-24 md:py-32 bg-gradient-to-br from-[#C41E3A] to-[#A01830] overflow-hidden"
    >
      <div className="container-wide">
        <div className="max-w-3xl mx-auto text-center">
          {/* 标题 */}
          <motion.h2
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6 }}
            className="text-3xl md:text-4xl font-bold text-white mb-6"
          >
            准备好提升企业运营效率了吗?
          </motion.h2>

          {/* 描述 */}
          <motion.p
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.1 }}
            className="text-lg text-white/90 mb-10"
          >
            立即联系我们,获取专属解决方案
          </motion.p>

          {/* 按钮 */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={isInView ? { opacity: 1, y: 0 } : {}}
            transition={{ duration: 0.6, delay: 0.2 }}
            className="flex flex-col sm:flex-row gap-4 justify-center"
          >
            <MagneticButton
              href="/contact"
              className="bg-white text-[#C41E3A] hover:bg-white/90 px-8 py-4 rounded-lg text-lg font-semibold"
            >
              立即咨询
            </MagneticButton>
            <MagneticButton
              href="tel:+8613800138000"
              className="bg-transparent border-2 border-white text-white hover:bg-white/10 px-8 py-4 rounded-lg text-lg font-semibold"
            >
              电话咨询
            </MagneticButton>
          </motion.div>
        </div>
      </div>
    </section>
  );
}
  • 步骤 2Commit
git add src/components/products/product-cta-section.tsx
git commit -m "feat(products): add product CTA section"

任务 9:创建统一导出文件

文件:

  • 创建:src/components/products/index.ts

  • 步骤 1:创建 index.ts

export { ProductHeroSection } from './product-hero-section';
export { ProductOverviewSection } from './product-overview-section';
export { ProductFeaturesSection } from './product-features-section';
export { ProductBenefitsSection } from './product-benefits-section';
export { ProductProcessSection } from './product-process-section';
export { ProductSpecsSection } from './product-specs-section';
export { ProductPricingSection } from './product-pricing-section';
export { ProductCTASection } from './product-cta-section';
  • 步骤 2Commit
git add src/components/products/index.ts
git commit -m "feat(products): add products components index"

任务 10:创建产品详情客户端组件

文件:

  • 创建:src/app/(marketing)/products/[id]/product-detail-client.tsx

  • 步骤 1:创建 product-detail-client.tsx

'use client';

import { useEffect, useState } from 'react';
import dynamic from 'next/dynamic';
import { PRODUCTS, type Product } from '@/lib/constants/products';

// 动态导入所有 section 组件
const ProductHeroSection = dynamic(
  () => import('@/components/products/product-hero-section').then(mod => ({ default: mod.ProductHeroSection })),
  { ssr: false }
);

const ProductOverviewSection = dynamic(
  () => import('@/components/products/product-overview-section').then(mod => ({ default: mod.ProductOverviewSection })),
  { ssr: false }
);

const ProductFeaturesSection = dynamic(
  () => import('@/components/products/product-features-section').then(mod => ({ default: mod.ProductFeaturesSection })),
  { ssr: false }
);

const ProductBenefitsSection = dynamic(
  () => import('@/components/products/product-benefits-section').then(mod => ({ default: mod.ProductBenefitsSection })),
  { ssr: false }
);

const ProductProcessSection = dynamic(
  () => import('@/components/products/product-process-section').then(mod => ({ default: mod.ProductProcessSection })),
  { ssr: false }
);

const ProductSpecsSection = dynamic(
  () => import('@/components/products/product-specs-section').then(mod => ({ default: mod.ProductSpecsSection })),
  { ssr: false }
);

const ProductPricingSection = dynamic(
  () => import('@/components/products/product-pricing-section').then(mod => ({ default: mod.ProductPricingSection })),
  { ssr: false }
);

const ProductCTASection = dynamic(
  () => import('@/components/products/product-cta-section').then(mod => ({ default: mod.ProductCTASection })),
  { ssr: false }
);

interface ProductDetailClientProps {
  productId: string;
}

export function ProductDetailClient({ productId }: ProductDetailClientProps) {
  const [product, setProduct] = useState<Product | null>(null);
  const [isDarkHeader, setIsDarkHeader] = useState(true);

  useEffect(() => {
    const found = PRODUCTS.find(p => p.id === productId);
    setProduct(found || null);
  }, [productId]);

  // 监听滚动,控制导航栏颜色
  useEffect(() => {
    const handleScroll = () => {
      const heroHeight = window.innerHeight;
      setIsDarkHeader(window.scrollY < heroHeight * 0.8);
    };

    window.addEventListener('scroll', handleScroll, { passive: true });
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  // 设置导航栏颜色(通过 CSS 变量或全局状态)
  useEffect(() => {
    document.documentElement.setAttribute('data-header-theme', isDarkHeader ? 'dark' : 'light');
  }, [isDarkHeader]);

  if (!product) {
    return (
      <div className="min-h-screen flex items-center justify-center bg-white">
        <p className="text-[#5C5C5C]">加载中...</p>
      </div>
    );
  }

  return (
    <main className="min-h-screen">
      <ProductHeroSection product={product} />
      <ProductOverviewSection product={product} />
      <ProductFeaturesSection product={product} />
      <ProductBenefitsSection product={product} />
      <ProductProcessSection product={product} />
      <ProductSpecsSection product={product} />
      <ProductPricingSection product={product} />
      <ProductCTASection />
    </main>
  );
}
  • 步骤 2Commit
git add src/app/\(marketing\)/products/\[id\]/product-detail-client.tsx
git commit -m "feat(products): add product detail client component"

任务 11:修改产品详情页面入口

文件:

  • 修改:src/app/(marketing)/products/[id]/page.tsx

  • 步骤 1:修改 page.tsx

将现有内容替换为:

import { notFound } from 'next/navigation';
import { PRODUCTS } from '@/lib/constants';
import { ProductDetailClient } from './product-detail-client';

export async function generateStaticParams() {
  return PRODUCTS.map((product) => ({
    id: product.id,
  }));
}

export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const product = PRODUCTS.find((p) => p.id === id);
  
  if (!product) {
    return {
      title: '产品未找到',
    };
  }

  return {
    title: `${product.title} - 睿新致远`,
    description: product.description,
  };
}

export default async function ProductDetailPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const product = PRODUCTS.find((p) => p.id === id);

  if (!product) {
    notFound();
  }

  return <ProductDetailClient productId={id} />;
}
  • 步骤 2Commit
git add src/app/\(marketing\)/products/\[id\]/page.tsx
git commit -m "refactor(products): update product detail page to use client component"

任务 12:修改 Header 组件支持深色模式

文件:

  • 修改:src/components/layout/header.tsx

  • 步骤 1:修改 Header 组件

HeaderContent 函数中,找到导航栏的 className 定义,修改为支持深色模式:

// 在 HeaderContent 函数开头添加
const [headerTheme, setHeaderTheme] = useState<'light' | 'dark'>('light');

useEffect(() => {
  const handleThemeChange = () => {
    const theme = document.documentElement.getAttribute('data-header-theme');
    setHeaderTheme(theme === 'dark' ? 'dark' : 'light');
  };

  // 初始检查
  handleThemeChange();

  // 使用 MutationObserver 监听属性变化
  const observer = new MutationObserver(handleThemeChange);
  observer.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ['data-header-theme'],
  });

  return () => observer.disconnect();
}, []);

// 修改 header 的 className
<header 
  className={`
    fixed top-0 left-0 right-0 z-50
    transition-all duration-300 ease-out
    ${headerTheme === 'dark'
      ? isScrolled 
        ? 'bg-[#0A0A0A]/90 backdrop-blur-xl border-b border-[#1A1A1A]'
        : 'bg-transparent'
      : isScrolled 
        ? 'bg-white/95 backdrop-blur-xl border-b border-[#E2E8F0] shadow-sm' 
        : 'bg-transparent'
    }
  `}
>

同时修改 Logo 和导航链接的颜色:

// Logo 颜色
<Image 
  src={headerTheme === 'dark' ? '/logo-white.svg' : '/logo.svg'}
  alt={COMPANY_INFO.name}
  // ... 其他属性
/>

// 导航链接颜色
<StaticLink
  href={item.href}
  className={`
    ...
    ${headerTheme === 'dark' ? 'text-white' : 'text-[#1C1C1C]'}
    ...
  `}
>
  • 步骤 2Commit
git add src/components/layout/header.tsx
git commit -m "feat(layout): add dark mode support to header component"

任务 13:验证构建

  • 步骤 1:运行构建
npm run build

预期:构建成功,无错误

  • 步骤 2:本地预览
npm run start

访问 http://localhost:3000/products/erp 验证效果

  • 步骤 3:最终 Commit
git add -A
git commit -m "feat(products): complete ERP product landing page with Terminal-style design"

规格覆盖度检查

规格章节 对应任务
Section 1: 全屏沉浸 Hero 任务 1
Section 2: 产品概述 任务 2
Section 3: 核心功能(滚动叙事) 任务 3
Section 4: 产品优势 任务 4
Section 5: 实施流程 任务 5
Section 6: 技术规格 任务 6
Section 7: 定价方案 任务 7
Section 8: CTA 横幅 任务 8
导航栏动态变色 任务 12
文件结构 任务 9, 10, 11

所有规格需求已覆盖


计划版本: 1.0 最后更新: 2026-04-24