Files
novalon-website/docs/plans/2026-02-26-website-feature-completion-implementation.md
T
张翔 3de9890fc4 fix: 修复TypeScript类型错误
- 移除未使用的导入
- 修复产品详情页面的description类型错误
- 修复服务详情页面的description类型错误
- 修复联系表单API的类型错误
- 添加Award图标的导入
2026-02-26 16:26:40 +08:00

47 KiB

网站功能补全实施计划

For Claude: REQUIRED SUB-SKILL: Use subagent-driven-development to implement this plan task-by-task.

Goal: 完善Novalon网站功能,包括产品详情页面、服务详情页面、联系表单邮件服务集成、隐私政策和服务条款页面

Architecture:

  • 使用Next.js 16 App Router创建动态路由页面
  • 使用Resend API集成邮件服务
  • 扩展constants.ts中的数据结构
  • 保持与现有页面一致的视觉风格

Tech Stack:

  • Next.js 16.1.6 + React 19.2.3 + TypeScript 5
  • Tailwind CSS v4 + CSS Variables
  • shadcn/ui组件库
  • Resend邮件服务(每月3000封免费)
  • Zod表单验证

阶段一:数据准备

Task 1: 扩展PRODUCTS数据结构

Files:

  • Modify: src/lib/constants.ts:112-154

Step 1: 查看当前PRODUCTS数据结构

Read src/lib/constants.ts lines 112-154 to understand current structure.

Expected: See current PRODUCTS array with id, title, description, category, features, benefits.

Step 2: 扩展PRODUCTS数据结构

为每个产品添加以下字段:

  • technicalArchitecture: { frontend: string[], backend: string[], database: string[], deployment: string[] }
  • scenarios: { title: string, description: string }[]
  • cases: { client: string, industry: string, results: { label: string, value: string }[] }[]
  • fullDescription: string (用于详情页的完整描述)
  • keyFeatures: string[] (详细功能列表)

Step 3: 提供完整的产品数据

为ERP、CRM、CMS、BI四个产品提供完整数据,包括技术架构、应用场景、客户案例等。

Expected: PRODUCTS数组包含完整的产品详细信息。

Step 4: 验证数据结构

npm run dev

Expected: 开发服务器启动,没有TypeScript错误。

Step 5: Commit

git add src/lib/constants.ts
git commit -m "feat: extend PRODUCTS data structure with technical architecture and cases"

Task 2: 扩展SERVICES数据结构

Files:

  • Modify: src/lib/constants.ts:73-110

Step 1: 查看当前SERVICES数据结构

Read src/lib/constants.ts lines 73-110 to understand current structure.

Expected: See current SERVICES array with id, title, description, icon.

Step 2: 扩展SERVICES数据结构

为每个服务添加以下字段:

  • content: { scope: string, process: { phase: string, description: string }[], deliverables: string[] }
  • advantages: { label: string, value: string }[]
  • fullDescription: string (用于详情页的完整描述)
  • relatedServices: string[] (相关服务ID列表)

Step 3: 提供完整的服务数据

为软件开发、云服务、数据分析、信息安全四个服务提供完整数据,包括服务内容、优势、流程等。

Expected: SERVICES数组包含完整的服务详细信息。

Step 4: 验证数据结构

npm run dev

Expected: 开发服务器启动,没有TypeScript错误。

Step 5: Commit

git add src/lib/constants.ts
git commit -m "feat: extend SERVICES data structure with content, advantages, and process"

阶段二:页面开发

Task 3: 创建产品详情页面组件

Files:

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

Step 1: 创建产品详情页面组件

'use client';

import { PRODUCTS } from '@/lib/constants';
import { notFound } from 'next/navigation';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ArrowLeft, Check, Code, Database, Cloud, Server } from 'lucide-react';
import Link from 'next/link';

interface ProductDetailPageProps {
  params: Promise<{
    id: string;
  }>;
}

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

export async function generateMetadata({ params }: ProductDetailPageProps) {
  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 }: ProductDetailPageProps) {
  const { id } = await params;
  const product = PRODUCTS.find((p) => p.id === id);
  
  if (!product) {
    notFound();
  }

  return (
    <div className="pt-32 pb-20">
      <div className="container-custom">
        <div className="max-w-5xl mx-auto">
          {/* 返回按钮 */}
          <Button variant="ghost" asChild className="mb-8">
            <Link href="/products">
              <ArrowLeft className="mr-2 w-4 h-4" />
              返回产品列表
            </Link>
          </Button>

          {/* 产品标题 */}
          <div className="mb-8">
            <Badge variant="secondary" className="mb-4">{product.category}</Badge>
            <h1 className="text-4xl sm:text-5xl font-bold text-black mb-4">{product.title}</h1>
            <p className="text-xl text-gray-600">{product.fullDescription}</p>
          </div>

          {/* 功能特性 */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>核心功能</CardTitle>
            </CardHeader>
            <CardContent>
              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                {product.features.map((feature, idx) => (
                  <div key={idx} className="flex items-start gap-2">
                    <Check className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
                    <span className="text-gray-700">{feature}</span>
                  </div>
                ))}
              </div>
            </CardContent>
          </Card>

          {/* 技术架构 */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>技术架构</CardTitle>
            </CardHeader>
            <CardContent>
              <div className="space-y-4">
                <div>
                  <h3 className="font-semibold text-black mb-2 flex items-center">
                    <Code className="w-5 h-5 mr-2 text-blue-600" />
                    前端技术
                  </h3>
                  <div className="flex flex-wrap gap-2">
                    {product.technicalArchitecture.frontend.map((tech, idx) => (
                      <Badge key={idx} variant="outline">{tech}</Badge>
                    ))}
                  </div>
                </div>
                <div>
                  <h3 className="font-semibold text-black mb-2 flex items-center">
                    <Server className="w-5 h-5 mr-2 text-purple-600" />
                    后端技术
                  </h3>
                  <div className="flex flex-wrap gap-2">
                    {product.technicalArchitecture.backend.map((tech, idx) => (
                      <Badge key={idx} variant="outline">{tech}</Badge>
                    ))}
                  </div>
                </div>
                <div>
                  <h3 className="font-semibold text-black mb-2 flex items-center">
                    <Database className="w-5 h-5 mr-2 text-orange-600" />
                    数据库
                  </h3>
                  <div className="flex flex-wrap gap-2">
                    {product.technicalArchitecture.database.map((tech, idx) => (
                      <Badge key={idx} variant="outline">{tech}</Badge>
                    ))}
                  </div>
                </div>
                <div>
                  <h3 className="font-semibold text-black mb-2 flex items-center">
                    <Cloud className="w-5 h-5 mr-2 text-cyan-600" />
                    部署方式
                  </h3>
                  <div className="flex flex-wrap gap-2">
                    {product.technicalArchitecture.deployment.map((tech, idx) => (
                      <Badge key={idx} variant="outline">{tech}</Badge>
                    ))}
                  </div>
                </div>
              </div>
            </CardContent>
          </Card>

          {/* 应用场景 */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>应用场景</CardTitle>
            </CardHeader>
            <CardContent className="space-y-4">
              {product.scenarios.map((scenario, idx) => (
                <div key={idx} className="border-l-4 border-black pl-4">
                  <h3 className="font-semibold text-black mb-1">{scenario.title}</h3>
                  <p className="text-gray-600">{scenario.description}</p>
                </div>
              ))}
            </CardContent>
          </Card>

          {/* 客户案例 */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>客户案例</CardTitle>
            </CardHeader>
            <CardContent className="space-y-4">
              {product.cases.map((caseItem, idx) => (
                <div key={idx} className="border border-gray-200 rounded-lg p-4">
                  <div className="flex items-center gap-2 mb-3">
                    <Badge variant="secondary">{caseItem.industry}</Badge>
                  </div>
                  <h3 className="font-semibold text-black mb-3">{caseItem.client}</h3>
                  <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
                    {caseItem.results.map((result, resIdx) => (
                      <div key={resIdx} className="text-center">
                        <div className="text-2xl font-bold text-black">{result.value}</div>
                        <div className="text-sm text-gray-600">{result.label}</div>
                      </div>
                    ))}
                  </div>
                </div>
              ))}
            </CardContent>
          </Card>

          {/* 底部CTA */}
          <div className="text-center">
            <Button size="lg" className="bg-black text-white hover:bg-gray-800">
              申请演示
              <ArrowLeft className="ml-2 w-4 h-4 rotate-180" />
            </Button>
          </div>
        </div>
      </div>
    </div>
  );
}

Step 2: 验证页面

npm run dev

Expected: 访问 /products/erp 能正常显示产品详情。

Step 3: Commit

git add src/app/(marketing)/products/[id]/page.tsx
git commit -m "feat: add product detail page with features, architecture, scenarios, and cases"

Task 4: 创建服务详情页面组件

Files:

  • Create: src/app/(marketing)/services/[id]/page.tsx

Step 1: 创建服务详情页面组件

'use client';

import { SERVICES } from '@/lib/constants';
import { notFound } from 'next/navigation';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ArrowLeft, Check, Clock } from 'lucide-react';
import Link from 'next/link';

interface ServiceDetailPageProps {
  params: Promise<{
    id: string;
  }>;
}

export function generateStaticParams() {
  return SERVICES.map((service) => ({
    id: service.id,
  }));
}

export async function generateMetadata({ params }: ServiceDetailPageProps) {
  const { id } = await params;
  const service = SERVICES.find((s) => s.id === id);
  
  if (!service) {
    return {
      title: `服务未找到`,
    };
  }
  
  return {
    title: `${service.title} - 服务详情`,
    description: service.description,
  };
}

export default async function ServiceDetailPage({ params }: ServiceDetailPageProps) {
  const { id } = await params;
  const service = SERVICES.find((s) => s.id === id);
  
  if (!service) {
    notFound();
  }

  return (
    <div className="pt-32 pb-20">
      <div className="container-custom">
        <div className="max-w-5xl mx-auto">
          {/* 返回按钮 */}
          <Button variant="ghost" asChild className="mb-8">
            <Link href="/services">
              <ArrowLeft className="mr-2 w-4 h-4" />
              返回服务列表
            </Link>
          </Button>

          {/* 服务标题 */}
          <div className="mb-8">
            <h1 className="text-4xl sm:text-5xl font-bold text-black mb-4">{service.title}</h1>
            <p className="text-xl text-gray-600">{service.fullDescription}</p>
          </div>

          {/* 服务内容 */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>服务内容</CardTitle>
            </CardHeader>
            <CardContent className="space-y-6">
              <div>
                <h3 className="font-semibold text-black mb-2">服务范围</h3>
                <p className="text-gray-600 leading-relaxed">{service.content.scope}</p>
              </div>
              
              <div>
                <h3 className="font-semibold text-black mb-2">服务流程</h3>
                <div className="space-y-4">
                  {service.content.process.map((phase, idx) => (
                    <div key={idx} className="flex gap-4">
                      <div className="flex-shrink-0 w-8 h-8 bg-black rounded-full flex items-center justify-center text-white font-medium">
                        {idx + 1}
                      </div>
                      <div>
                        <h4 className="font-semibold text-black">{phase.phase}</h4>
                        <p className="text-gray-600">{phase.description}</p>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
              
              <div>
                <h3 className="font-semibold text-black mb-2">交付成果</h3>
                <div className="flex flex-wrap gap-2">
                  {service.content.deliverables.map((item, idx) => (
                    <Badge key={idx} variant="secondary">{item}</Badge>
                  ))}
                </div>
              </div>
            </CardContent>
          </Card>

          {/* 服务优势 */}
          <Card className="mb-8">
            <CardHeader>
              <CardTitle>服务优势</CardTitle>
            </CardHeader>
            <CardContent>
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
                {service.advantages.map((advantage, idx) => (
                  <div key={idx} className="flex items-start gap-2">
                    <Check className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
                    <div>
                      <div className="font-semibold text-black">{advantage.label}</div>
                      <div className="text-gray-600">{advantage.value}</div>
                    </div>
                  </div>
                ))}
              </div>
            </CardContent>
          </Card>

          {/* 相关服务 */}
          {service.relatedServices && service.relatedServices.length > 0 && (
            <Card className="mb-8">
              <CardHeader>
                <CardTitle>相关服务</CardTitle>
              </CardHeader>
              <CardContent>
                <div className="flex flex-wrap gap-2">
                  {service.relatedServices.map((relatedId, idx) => {
                    const relatedService = SERVICES.find(s => s.id === relatedId);
                    if (!relatedService) return null;
                    return (
                      <Button key={idx} variant="outline" asChild>
                        <Link href={`/services/${relatedId}`}>
                          {relatedService.title}
                        </Link>
                      </Button>
                    );
                  })}
                </div>
              </CardContent>
            </Card>
          )}

          {/* 底部CTA */}
          <div className="text-center">
            <Button size="lg" className="bg-black text-white hover:bg-gray-800" asChild>
              <Link href="/contact">
                立即咨询
                <ArrowLeft className="ml-2 w-4 h-4 rotate-180" />
              </Link>
            </Button>
          </div>
        </div>
      </div>
    </div>
  );
}

Step 2: 验证页面

npm run dev

Expected: 访问 /services/software 能正常显示服务详情。

Step 3: Commit

git add src/app/(marketing)/services/[id]/page.tsx
git commit -m "feat: add service detail page with content, advantages, and process"

Task 5: 创建隐私政策页面

Files:

  • Create: src/app/(marketing)/privacy/page.tsx

Step 1: 创建隐私政策页面

import { COMPANY_INFO } from '@/lib/constants';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

export const metadata = {
  title: `隐私政策 - ${COMPANY_INFO.name}`,
  description: `了解${COMPANY_INFO.name}如何收集、使用和保护您的个人信息`,
};

export default function PrivacyPolicyPage() {
  return (
    <div className="pt-32 pb-20">
      <div className="container-custom max-w-4xl mx-auto">
        <div className="text-center mb-12">
          <h1 className="text-4xl sm:text-5xl font-bold text-black mb-6">隐私政策</h1>
          <p className="text-lg text-gray-600">最后更新日期: 2026年2月26日</p>
        </div>

        <Card>
          <CardContent className="p-8 space-y-8">
            {/* 引言 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">1. 引言</h2>
              <p className="text-gray-600 leading-relaxed">
                欢迎访问四川睿新致远科技有限公司(以下简称"我们""公司")的官方网站。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的个人信息,以及您对个人信息所享有的权利。
              </p>
              <p className="text-gray-600 leading-relaxed mt-4">
                本隐私政策适用于您访问和使用我们网站时所涉及的个人信息处理活动。通过访问和使用我们的网站,您同意本隐私政策的内容。如果您不同意本隐私政策,请立即停止使用我们的网站。
              </p>
            </section>

            {/* 信息收集 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">2. 信息收集</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们在您使用网站服务时,可能收集以下类型的个人信息:
              </p>
              
              <div className="space-y-4">
                <div>
                  <h3 className="font-semibold text-black mb-2">2.1 您主动提供的信息</h3>
                  <p className="text-gray-600 leading-relaxed">
                    当您填写联系表单、发送邮件、订阅我们的服务或参与我们的活动时,我们可能会收集您的以下信息:
                  </p>
                  <ul className="list-disc list-inside text-gray-600 leading-relaxed mt-2 space-y-1">
                    <li>姓名</li>
                    <li>电子邮件地址</li>
                    <li>电话号码</li>
                    <li>公司名称</li>
                    <li>职位</li>
                    <li>其他您主动提供的信息</li>
                  </ul>
                </div>

                <div>
                  <h3 className="font-semibold text-black mb-2">2.2 自动收集的信息</h3>
                  <p className="text-gray-600 leading-relaxed">
                    当您访问我们的网站时,我们可能会自动收集以下信息:
                  </p>
                  <ul className="list-disc list-inside text-gray-600 leading-relaxed mt-2 space-y-1">
                    <li>IP地址</li>
                    <li>浏览器类型和版本</li>
                    <li>操作系统类型和版本</li>
                    <li>访问时间、页面浏览记录</li>
                    <li>设备标识符</li>
                    <li>Cookie信息</li>
                  </ul>
                </div>
              </div>
            </section>

            {/* 信息使用 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">3. 信息使用</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们将收集的信息用于以下目的:
              </p>
              <ul className="list-disc list-inside text-gray-600 leading-relaxed space-y-1">
                <li>为您提供网站服务和功能</li>
                <li>处理您的咨询、反馈和请求</li>
                <li>与您沟通,包括发送通知和回复您的问题</li>
                <li>改进我们的网站和服务</li>
                <li>进行数据分析和研究</li>
                <li>保护网站的安全和防止欺诈</li>
                <li>遵守适用的法律法规</li>
              </ul>
            </section>

            {/* 信息共享 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">4. 信息共享</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们不会出售或出租您的个人信息。在以下情况下,我们可能会共享您的信息:
              </p>
              <ul className="list-disc list-inside text-gray-600 leading-relaxed space-y-1">
                <li>获得您的明确同意</li>
                <li>为履行法律法规规定的义务</li>
                <li>为保护我们的合法权益或他人权益</li>
                <li>与我们的关联公司、合作伙伴共享(仅限必要范围)</li>
                <li>与第三方服务提供商共享(如邮件服务、分析服务)</li>
              </ul>
            </section>

            {/* 信息存储 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">5. 信息存储</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们将采取合理措施保护您的个人信息,防止信息丢失、泄露或被未经授权访问。
              </p>
              <p className="text-gray-600 leading-relaxed">
                我们将根据以下原则确定个人信息的存储期限:
              </p>
              <ul className="list-disc list-inside text-gray-600 leading-relaxed space-y-1 mt-2">
                <li>为实现本隐私政策所述的目的所必需的期限</li>
                <li>为遵守适用的法律法规所必需的期限</li>
                <li>为解决争议、履行法律责任所必需的期限</li>
              </ul>
            </section>

            {/* 用户权利 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">6. 用户权利</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                您对您的个人信息享有以下权利:
              </p>
              <ul className="list-disc list-inside text-gray-600 leading-relaxed space-y-1">
                <li>访问权:您可以请求访问您的个人信息</li>
                <li>更正权:您可以请求更正您的个人信息</li>
                <li>删除权:您可以请求删除您的个人信息</li>
                <li>限制处理权:您可以请求限制对您的个人信息的处理</li>
                <li>数据可携权:您可以请求获取您的个人信息副本</li>
                <li>撤回同意权:您可以随时撤回对个人信息处理的同意</li>
              </ul>
              <p className="text-gray-600 leading-relaxed mt-4">
                如您行使上述权利,请通过本隐私政策第10节提供的联系方式与我们联系。
              </p>
            </section>

            {/* Cookie使用 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">7. Cookie使用</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们网站使用Cookie和其他类似技术来提供服务和改进用户体验Cookie是存储在您设备上的小数据文件,用于识别您的浏览器或设备。
              </p>
              <p className="text-gray-600 leading-relaxed">
                您可以通过浏览器设置拒绝或管理Cookie。但请注意,如果您禁用Cookie,可能会影响网站某些功能的正常使用。
              </p>
            </section>

            {/* 未成年人保护 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">8. 未成年人保护</h2>
              <p className="text-gray-600 leading-relaxed">
                我们重视对未成年人个人信息的保护。我们的网站和服务不面向14岁以下的儿童。如果我们发现我们收集了14岁以下儿童的个人信息,我们将尽快删除相关信息。如果您是未成年人的监护人,并且认为我们收集了您监护的儿童的个人信息,请与我们联系。
              </p>
            </section>

            {/* 政策更新 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">9. 政策更新</h2>
              <p className="text-gray-600 leading-relaxed">
                我们可能会不定期更新本隐私政策。更新后的隐私政策将发布在本页面上,并注明更新日期。我们建议您定期查看本隐私政策,以了解我们如何保护您的个人信息。如果您不同意更新后的隐私政策,请停止使用我们的网站。
              </p>
            </section>

            {/* 联系方式 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">10. 联系方式</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                如您对本隐私政策有任何疑问或意见,或需要行使您的个人信息权利,请通过以下方式与我们联系:
              </p>
              <div className="bg-gray-50 p-6 rounded-lg">
                <p className="text-gray-600 leading-relaxed">
                  <strong className="text-black">公司名称:</strong> {COMPANY_INFO.name}<br />
                  <strong className="text-black">地址:</strong> {COMPANY_INFO.address}<br />
                  <strong className="text-black">电话:</strong> {COMPANY_INFO.phone}<br />
                  <strong className="text-black">邮箱:</strong> {COMPANY_INFO.email}
                </p>
              </div>
            </section>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

Step 2: 验证页面

npm run dev

Expected: 访问 /privacy 能正常显示隐私政策页面。

Step 3: Commit

git add src/app/(marketing)/privacy/page.tsx
git commit -m "feat: add privacy policy page with comprehensive sections"

Task 6: 创建服务条款页面

Files:

  • Create: src/app/(marketing)/terms/page.tsx

Step 1: 创建服务条款页面

import { COMPANY_INFO } from '@/lib/constants';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

export const metadata = {
  title: `服务条款 - ${COMPANY_INFO.name}`,
  description: `使用${COMPANY_INFO.name}网站服务前请仔细阅读本服务条款`,
};

export default function TermsOfServicePage() {
  return (
    <div className="pt-32 pb-20">
      <div className="container-custom max-w-4xl mx-auto">
        <div className="text-center mb-12">
          <h1 className="text-4xl sm:text-5xl font-bold text-black mb-6">服务条款</h1>
          <p className="text-lg text-gray-600">最后更新日期: 2026年2月26日</p>
        </div>

        <Card>
          <CardContent className="p-8 space-y-8">
            {/* 引言 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">1. 引言</h2>
              <p className="text-gray-600 leading-relaxed">
                欢迎访问四川睿新致远科技有限公司(以下简称"我们""公司")的官方网站。本服务条款旨在向您说明您使用我们网站服务时应遵守的规则和条款。
              </p>
              <p className="text-gray-600 leading-relaxed mt-4">
                本服务条款适用于您访问和使用我们网站时所涉及的所有行为。通过访问和使用我们的网站,您同意本服务条款的内容。如果您不同意本服务条款,请立即停止使用我们的网站。
              </p>
            </section>

            {/* 服务内容 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">2. 服务内容</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们为用户提供以下网站服务:
              </p>
              <ul className="list-disc list-inside text-gray-600 leading-relaxed space-y-1">
                <li>公司信息展示</li>
                <li>产品和服务介绍</li>
                <li>新闻动态和行业资讯</li>
                <li>在线联系和咨询</li>
                <li>其他我们不时提供的服务</li>
              </ul>
              <p className="text-gray-600 leading-relaxed mt-4">
                我们保留随时修改、中断或终止全部或部分网站服务的权利,无需事先通知用户。
              </p>
            </section>

            {/* 用户义务 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">3. 用户义务</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                您在使用我们的网站服务时,应当遵守以下义务:
              </p>
              <ul className="list-disc list-inside text-gray-600 leading-relaxed space-y-1">
                <li>遵守中华人民共和国相关法律法规和政策</li>
                <li>遵守本服务条款的各项条款</li>
                <li>不得侵犯他人的合法权益(包括知识产权、隐私权等)</li>
                <li>不得发布违法、有害、虚假、暴力、色情等内容</li>
                <li>不得干扰或破坏网站服务,不得使用非我们授权的工具访问网站</li>
                <li>不得以任何方式获取网站未授权访问的数据或信息</li>
                <li>如实提供个人资料,并及时更新资料</li>
              </ul>
            </section>

            {/* 知识产权 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">4. 知识产权</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                网站上所有内容(包括但不限于文字、图片、音频、视频、软件、代码等)的知识产权均归我们或相关权利人所有。
              </p>
              <p className="text-gray-600 leading-relaxed">
                未经我们或相关权利人的书面许可,您不得以任何形式复制、转载、引用、修改、创建衍生作品或用于商业用途。但您可为个人非商业目的下载、打印或存储少量内容。
              </p>
            </section>

            {/* 免责条款 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">5. 免责条款</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们尽力确保网站的准确性和可靠性,但不对网站信息的准确性、完整性、及时性、可靠性作出任何保证。
              </p>
              <p className="text-gray-600 leading-relaxed">
                在任何情况下,我们都不对因使用或无法使用网站服务而导致的任何直接、间接、偶然、特殊或惩罚性损害承担责任,包括但不限于利润损失、数据丢失、业务中断等。
              </p>
            </section>

            {/* 服务变更 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">6. 服务变更</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                我们保留随时修改、中断或终止全部或部分网站服务的权利,包括但不限于修改网站内容、功能、服务范围等。
              </p>
              <p className="text-gray-600 leading-relaxed">
                我们将在网站上发布变更通知,但您理解并同意,我们无需对任何因服务变更导致的损失承担责任。
              </p>
            </section>

            {/* 争议解决 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">7. 争议解决</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                本服务条款的订立、执行、解释及争议解决,均适用中华人民共和国法律。
              </p>
              <p className="text-gray-600 leading-relaxed">
                如因本服务条款产生争议,双方应友好协商解决;协商不成的,任何一方均有权向有管辖权的人民法院提起诉讼。
              </p>
            </section>

            {/* 条款更新 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">8. 条款更新</h2>
              <p className="text-gray-600 leading-relaxed">
                我们可能会不定期更新本服务条款。更新后的服务条款将发布在本页面上,并注明更新日期。我们建议您定期查看本服务条款,以了解您使用网站服务时应遵守的规则。如果您不同意更新后的服务条款,请停止使用我们的网站。
              </p>
            </section>

            {/* 联系方式 */}
            <section>
              <h2 className="text-2xl font-bold text-black mb-4">9. 联系方式</h2>
              <p className="text-gray-600 leading-relaxed mb-4">
                如您对本服务条款有任何疑问或意见,请通过以下方式与我们联系:
              </p>
              <div className="bg-gray-50 p-6 rounded-lg">
                <p className="text-gray-600 leading-relaxed">
                  <strong className="text-black">公司名称:</strong> {COMPANY_INFO.name}<br />
                  <strong className="text-black">地址:</strong> {COMPANY_INFO.address}<br />
                  <strong className="text-black">电话:</strong> {COMPANY_INFO.phone}<br />
                  <strong className="text-black">邮箱:</strong> {COMPANY_INFO.email}
                </p>
              </div>
            </section>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

Step 2: 验证页面

npm run dev

Expected: 访问 /terms 能正常显示服务条款页面。

Step 3: Commit

git add src/app/(marketing)/terms/page.tsx
git commit -m "feat: add terms of service page with comprehensive sections"

阶段三:功能集成

Task 7: 安装依赖

Files:

  • Modify: package.json

Step 1: 安装Resend和Zod

npm install resend zod

Expected: Resend和Zod添加到package.json的dependencies中。

Step 2: 验证安装

npm list resend zod

Expected: 显示Resend和Zod的版本号。

Step 3: Commit

git add package.json package-lock.json
git commit -m "chore: add resend and zod dependencies"

Task 8: 创建联系表单API路由

Files:

  • Create: src/app/api/contact/route.ts

Step 1: 创建API路由

import { NextRequest, NextResponse } from 'next/server';
import { Resend } from 'resend';
import { z } from 'zod';

// 定义表单数据验证schema
const contactFormSchema = z.object({
  name: z.string().min(1, '姓名不能为空').max(50, '姓名不能超过50个字符'),
  phone: z.string().optional(),
  email: z.string().email('邮箱格式不正确'),
  subject: z.string().min(1, '主题不能为空').max(200, '主题不能超过200个字符'),
  message: z.string().min(1, '消息内容不能为空').max(2000, '消息内容不能超过2000个字符'),
});

// 定义请求体类型
type ContactFormData = z.infer<typeof contactFormSchema>;

// 初始化Resend
const resend = new Resend(process.env.RESEND_API_KEY);

// 定义允许的来源
const ALLOWED_ORIGINS = [
  'https://novalon.cn',
  'https://www.novalon.cn',
  'http://localhost:3000',
];

// CORS中间件
function corsHeaders(origin: string | null) {
  const allowed = origin && ALLOWED_ORIGINS.includes(origin);
  return {
    'Access-Control-Allow-Origin': allowed ? origin : '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Max-Age': '86400',
  };
}

// 处理OPTIONS请求
export async function OPTIONS(request: NextRequest) {
  const origin = request.headers.get('origin');
  return NextResponse.json({}, { headers: corsHeaders(origin) });
}

// 处理POST请求
export async function POST(request: NextRequest) {
  const origin = request.headers.get('origin');
  
  // 检查Content-Type
  const contentType = request.headers.get('content-type');
  if (contentType !== 'application/json') {
    return NextResponse.json(
      { error: 'Content-Type必须为application/json' },
      { 
        status: 400,
        headers: corsHeaders(origin)
      }
    );
  }

  try {
    // 解析请求体
    const body = await request.json();
    
    // 验证数据
    const validationResult = contactFormSchema.safeParse(body);
    
    if (!validationResult.success) {
      const errors = validationResult.error.errors.map(err => ({
        field: err.path.join('.'),
        message: err.message
      }));
      
      return NextResponse.json(
        { error: '验证失败', errors },
        { 
          status: 400,
          headers: corsHeaders(origin)
        }
      );
    }

    const { name, phone, email, subject, message } = validationResult.data;

    // 检查频率限制(简单实现)
    const clientIp = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';
    const rateLimitKey = `contact_form:${clientIp}`;
    
    // 注意:生产环境应该使用Redis等持久化存储
    // 这里只是一个简单的内存限制示例
    // 实际实现时需要更完善的频率限制机制

    // 发送邮件
    const response = await resend.emails.send({
      from: process.env.FROM_EMAIL || 'No reply <noreply@resend.dev>',
      to: [process.env.CONTACT_EMAIL || 'contact@novalon.cn'],
      subject: `[${subject}] ${name}`,
      html: `
        <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
          <h2 style="color: #1a1a1a;">新联系表单提交</h2>
          
          <div style="background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0;">
            <p style="margin: 10px 0;"><strong>姓名:</strong> ${name}</p>
            ${phone ? `<p style="margin: 10px 0;"><strong>电话:</strong> ${phone}</p>` : ''}
            <p style="margin: 10px 0;"><strong>邮箱:</strong> ${email}</p>
            <p style="margin: 10px 0;"><strong>主题:</strong> ${subject}</p>
            <p style="margin: 10px 0;"><strong>消息内容:</strong></p>
            <div style="background: white; padding: 15px; border-radius: 4px; margin-top: 10px; white-space: pre-wrap;">${message}</div>
          </div>
          
          <p style="color: #666; font-size: 14px;">此邮件由网站联系表单自动发送,请勿直接回复。</p>
        </div>
      `,
    });

    // 发送确认邮件(可选)
    if (response.data) {
      try {
        await resend.emails.send({
          from: process.env.FROM_EMAIL || 'No reply <noreply@resend.dev>',
          to: [email],
          subject: '感谢您的联系 - 确认邮件',
          html: `
            <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
              <h2 style="color: #1a1a1a;">感谢您的联系!</h2>
              
              <p style="color: #333; margin: 20px 0;">您好 ${name},</p>
              
              <p style="color: #333; margin: 20px 0;">
                我们已收到您的消息,主题为"${subject}"。我们的团队会尽快查看并回复您。
              </p>
              
              <p style="color: #333; margin: 20px 0;">
                如果您有任何其他问题,欢迎随时联系我们。
              </p>
              
              <div style="background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0;">
                <p style="margin: 10px 0;"><strong>您提交的信息:</strong></p>
                <p style="margin: 10px 0;">主题: ${subject}</p>
                <p style="margin: 10px 0;">消息: ${message.substring(0, 200)}${message.length > 200 ? '...' : ''}</p>
              </div>
              
              <p style="color: #666; font-size: 14px;">此邮件由网站自动发送,请勿直接回复。</p>
            </div>
          `,
        });
      } catch (confirmError) {
        console.error('发送确认邮件失败:', confirmError);
        // 不影响主流程,仅记录错误
      }
    }

    return NextResponse.json(
      { success: true, message: '消息已发送成功' },
      { 
        status: 200,
        headers: corsHeaders(origin)
      }
    );

  } catch (error) {
    console.error('联系表单提交失败:', error);
    
    return NextResponse.json(
      { error: '消息发送失败,请稍后重试' },
      { 
        status: 500,
        headers: corsHeaders(origin)
      }
    );
  }
}

Step 2: 验证API路由

npm run dev

Expected: API路由创建成功,没有TypeScript错误。

Step 3: Commit

git add src/app/api/contact/route.ts
git commit -m "feat: add contact form API route with validation and email sending"

Task 9: 更新联系表单提交逻辑

Files:

  • Modify: src/app/(marketing)/contact/page.tsx

Step 1: 查看当前联系表单代码

Read src/app/(marketing)/contact/page.tsx to understand current implementation.

Step 2: 更新联系表单提交逻辑

// 替换handleSubmit函数
async function handleSubmit(formData: FormData) {
  setIsSubmitting(true);
  
  try {
    const response = await fetch('/api/contact', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        name: formData.get('name'),
        phone: formData.get('phone'),
        email: formData.get('email'),
        subject: formData.get('subject'),
        message: formData.get('message'),
      }),
    });

    const result = await response.json();

    if (!response.ok) {
      throw new Error(result.error || '消息发送失败');
    }

    setIsSubmitting(false);
    setIsSubmitted(true);
    
    // 3秒后重置表单
    setTimeout(() => {
      setIsSubmitted(false);
    }, 3000);

  } catch (error) {
    setIsSubmitting(false);
    setError(error instanceof Error ? error.message : '消息发送失败');
  }
}

Step 3: 添加错误显示

在表单中添加错误显示:

{error && (
  <div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
    <p className="text-red-600 text-sm">{error}</p>
  </div>
)}

Step 4: 验证表单

npm run dev

Expected: 联系表单能够成功提交并发送邮件。

Step 5: Commit

git add src/app/(marketing)/contact/page.tsx
git commit -m "feat: update contact form to use API route for email sending"

阶段四:测试和优化

Task 10: 运行代码检查

Files:

  • All modified files

Step 1: 运行ESLint

npm run lint

Expected: 所有文件通过ESLint检查,没有错误。

Step 2: 修复ESLint错误

根据ESLint输出,修复所有错误。

Step 3: 运行TypeScript类型检查

npx tsc --noEmit

Expected: 没有TypeScript类型错误。

Step 4: Commit

git add .
git commit -m "fix: fix linting and type errors"

Task 11: 运行开发服务器测试

Files:

  • All new and modified files

Step 1: 启动开发服务器

npm run dev

Expected: 开发服务器启动成功,没有错误。

Step 2: 测试所有页面

  • 访问 /products/erp - 测试产品详情页面
  • 访问 /services/software - 测试服务详情页面
  • 访问 /privacy - 测试隐私政策页面
  • 访问 /terms - 测试服务条款页面
  • 访问 /contact - 测试联系表单

Expected: 所有页面正常显示,功能正常。

Step 3: Commit

git add .
git commit -m "test: verify all pages and functionality"

Task 12: 响应式设计测试

Files:

  • All new pages

Step 1: 测试移动端

在浏览器开发者工具中,切换到移动设备模式,测试所有页面在手机上的显示效果。

Expected: 所有页面在移动端正常显示,布局合理。

Step 2: 测试平板

切换到平板设备模式,测试所有页面在平板上的显示效果。

Expected: 所有页面在平板上正常显示,布局合理。

Step 3: Commit

git add .
git commit -m "test: verify responsive design on mobile and tablet"

阶段五:部署和上线

Task 13: 配置环境变量

Files:

  • .env.local (not committed to git)

Step 1: 创建.env.local文件

cp .env.example .env.local

Step 2: 配置环境变量

.env.local中添加:

# Resend API配置
RESEND_API_KEY=re_123456789_your_actual_api_key
FROM_EMAIL=No reply <noreply@resend.dev>
CONTACT_EMAIL=contact@novalon.cn

Step 3: 验证环境变量

npm run dev

Expected: 开发服务器启动成功,邮件发送功能正常。

Step 4: Commit

git add .env.local
git commit -m "chore: add environment variables (do not commit this to public repo)"

Task 14: 构建生产版本

Files:

  • All files

Step 1: 构建生产版本

npm run build

Expected: 构建成功,没有错误。

Step 2: 验证构建输出

检查dist/目录,确保所有文件都已生成。

Step 3: Commit

git add .
git commit -m "build: create production build"

Task 15: 部署到生产环境

Files:

  • All files

Step 1: 部署

根据您的部署平台,部署生产版本。

Step 2: 验证生产环境

  • 访问生产环境的URL
  • 测试所有页面
  • 测试联系表单

Expected: 生产环境正常运行,所有功能正常。

Step 3: Commit

git add .
git commit -m "deploy: deploy to production environment"

验收检查清单

功能验收

  • 产品详情页面正常显示所有内容模块
  • 服务详情页面正常显示所有内容模块
  • 隐私政策和服务条款页面内容完整
  • 联系表单能够成功发送邮件
  • 表单验证和安全措施正常工作

质量验收

  • 所有页面通过ESLint检查
  • 所有页面通过TypeScript类型检查
  • 响应式设计在各种设备上正常显示
  • 页面加载性能符合要求
  • SEO优化符合最佳实践

部署验收

  • 生产环境部署成功
  • 所有页面在生产环境正常显示
  • 联系表单在生产环境正常工作
  • 环境变量配置正确

备注

  1. 所有环境变量文件.env.local不应提交到版本控制系统
  2. Resend API密钥需要在Resend控制台获取
  3. FROM_EMAIL需要在Resend中验证域名
  4. 建议在生产环境使用更完善的频率限制机制(如Redis)
  5. 建议添加表单提交日志,便于后续分析和追溯