diff --git a/docs/plans/2026-02-26-website-feature-completion-design.md b/docs/plans/2026-02-26-website-feature-completion-design.md new file mode 100644 index 0000000..dde701e --- /dev/null +++ b/docs/plans/2026-02-26-website-feature-completion-design.md @@ -0,0 +1,397 @@ +# 网站功能补全设计方案 + +**日期**: 2026-02-26 +**状态**: 已确认 +**优先级**: 高 + +## 概述 + +本文档描述了Novalon网站功能补全的完整设计方案,包括产品详情页面、服务详情页面、联系表单邮件服务集成、隐私政策和服务条款页面。 + +## 1. 产品详情页面 + +### 路由 +- 路径: `/products/[id]` +- 动态路由参数: `id` (产品ID,如 `erp`, `crm`, `cms`, `bi`) + +### 页面结构 + +#### 顶部区域 +- 产品标题 +- 分类标签 +- 简短描述 +- 产品图片或图标 + +#### 功能特性模块 +- 按模块分组展示功能点 +- 每个功能点配有图标和详细说明 +- 对于复杂产品,分组展示(如"财务模块"、"采购模块"等) + +#### 技术架构模块 +- 技术架构图 +- 技术栈说明 +- 前端技术、后端技术、数据库、部署方式 + +#### 应用场景模块 +- 典型应用场景 +- 行业案例 +- 解决方案说明 + +#### 客户案例模块 +- 成功客户案例 +- 客户名称、行业 +- 实施效果(数据化展示) + +#### 底部CTA +- "联系我们"按钮 +- "申请演示"按钮 + +### 数据来源 +- 从 `@/lib/constants` 中的 `PRODUCTS` 数组获取产品数据 +- 需要扩展 `PRODUCTS` 数据结构,添加详细内容 + +## 2. 服务详情页面 + +### 路由 +- 路径: `/services/[id]` +- 动态路由参数: `id` (服务ID,如 `software`, `cloud`, `data`, `security`) + +### 页面结构 + +#### 顶部区域 +- 服务标题 +- 服务图标 +- 服务概述 +- 价值主张 + +#### 服务内容模块 +- 服务范围详细说明 +- 服务流程 +- 交付标准 +- 对于软件开发服务,说明需求分析、设计、开发、测试、部署等各阶段 + +#### 服务优势模块 +- 核心优势 +- 技术实力 +- 行业经验 +- 团队规模 +- 成功案例 +- 数据化展示(如"10年+行业经验"、"100+成功案例") + +#### 服务流程模块 +- 时间轴形式展示 +- 从接触到交付的完整过程 + +#### 相关服务模块 +- 展示其他相关服务 +- 引导客户了解更多 + +#### 底部CTA +- "立即咨询"按钮 +- 跳转到联系页面并预填服务类型 + +### 数据来源 +- 从 `@/lib/constants` 中的 `SERVICES` 数组获取服务数据 +- 需要扩展 `SERVICES` 数据结构,添加详细内容 + +## 3. 联系表单邮件服务集成 + +### 技术选型 +- **主选方案**: Resend + - 每月3000封免费邮件 + - 简洁的API接口 + - 良好的开发者体验 + - 支持国内访问 + +- **替代方案**: + - EmailJS: 前端直接发送,每月200封免费 + - SendGrid: 每天100封免费 + - SMTP2GO: 每月1000封免费 + +### 实现方式 + +#### API路由 +- 路径: `/api/contact` +- 方法: POST +- 接收表单数据: name, phone, email, subject, message + +#### 邮件发送流程 +1. 接收表单数据 +2. 验证数据(Zod schema) +3. 调用Resend SDK发送邮件到公司邮箱 +4. (可选)发送确认邮件给用户 +5. 返回成功或失败响应 + +### 安全措施 +- 使用环境变量存储API密钥 (`RESEND_API_KEY`) +- 实现CSRF防护 +- 添加表单验证(Zod schema验证) +- 实现频率限制(每IP每小时最多5次) +- 使用简单验证码防止机器人提交 + +### 用户体验 +- 提交时显示加载状态 +- 提交成功后显示成功提示 +- 提交失败时显示错误信息并允许重试 +- 保留表单数据,失败时无需重新填写 + +### 配置管理 +- 在 `.env.local` 中配置: + - `RESEND_API_KEY`: Resend API密钥 + - `CONTACT_EMAIL`: 收件人邮箱 + - `FROM_EMAIL`: 发件人邮箱(需要在Resend中验证域名) + +### 依赖安装 +```bash +npm install resend +npm install zod +``` + +## 4. 隐私政策和服务条款页面 + +### 隐私政策页面 (`/privacy`) + +#### 页面结构 +1. **引言部分**: 适用范围和生效日期 +2. **信息收集**: 收集的用户信息类型及收集目的 +3. **信息使用**: 如何使用收集的信息 +4. **信息共享**: 在何种情况下会共享用户信息 +5. **信息存储**: 信息的存储方式和存储期限 +6. **用户权利**: 用户对个人信息享有的权利 +7. **Cookie使用**: Cookie的目的和管理方式 +8. **未成年人保护**: 对未成年人的特殊保护措施 +9. **政策更新**: 隐私政策的更新机制和通知方式 +10. **联系方式**: 数据保护负责人的联系方式 + +#### 法律依据 +- 《中华人民共和国网络安全法》 +- 《中华人民共和国个人信息保护法》 +- 《中华人民共和国数据安全法》 + +### 服务条款页面 (`/terms`) + +#### 页面结构 +1. **引言部分**: 适用范围和生效日期 +2. **服务内容**: 网站提供的服务内容和范围 +3. **用户义务**: 用户使用服务时应遵守的规则和限制 +4. **知识产权**: 网站内容的知识产权归属和使用限制 +5. **免责条款**: 网站在何种情况下不承担责任 +6. **服务变更**: 网站变更或终止服务的权利和通知方式 +7. **争议解决**: 争议解决的方式和适用法律 +8. **条款更新**: 服务条款的更新机制和通知方式 +9. **联系方式**: 法律事务联系人的联系方式 + +#### 法律依据 +- 《中华人民共和国民法典》 +- 《中华人民共和国电子商务法》 +- 《中华人民共和国网络安全法》 + +### 设计特点 +- 清晰的层级结构,便于阅读 +- 使用锚点导航,方便用户快速跳转到相关章节 +- 保持专业、严谨的法律文档风格 +- 响应式设计,适配各种设备 + +## 技术实现要点 + +### 通用要求 +- 所有页面使用Next.js App Router +- 组件复用现有的UI组件库(shadcn/ui) +- 保持与现有页面一致的视觉风格 +- 响应式设计,适配各种设备 +- SEO优化,包含metadata和结构化数据 + +### 文件结构 +``` +src/ +├── app/ +│ ├── (marketing)/ +│ │ ├── products/ +│ │ │ └── [id]/ +│ │ │ └── page.tsx # 产品详情页面 +│ │ ├── services/ +│ │ │ └── [id]/ +│ │ │ └── page.tsx # 服务详情页面 +│ │ ├── privacy/ +│ │ │ └── page.tsx # 隐私政策页面 +│ │ └── terms/ +│ │ └── page.tsx # 服务条款页面 +│ └── api/ +│ └── contact/ +│ └── route.ts # 联系表单API路由 +└── lib/ + └── constants.ts # 扩展PRODUCTS和SERVICES数据 +``` + +### 数据扩展 + +#### PRODUCTS数据结构扩展 +```typescript +export const PRODUCTS = [ + { + id: 'erp', + title: '睿新ERP管理系统', + description: '...', + category: '企业软件', + features: ['财务管理', '采购管理', ...], + benefits: ['提升运营效率30%', ...], + // 新增字段 + technicalArchitecture: { + frontend: ['React', 'TypeScript'], + backend: ['Node.js', 'Express'], + database: ['PostgreSQL', 'Redis'], + deployment: ['Docker', 'Kubernetes'] + }, + scenarios: [ + { + title: '制造业', + description: '实现生产计划、物料需求、库存管理的全流程数字化' + }, + // ... + ], + cases: [ + { + client: '某制造企业', + industry: '制造业', + results: [ + { label: '生产效率', value: '提升40%' }, + // ... + ] + }, + // ... + ] + }, + // ... +] +``` + +#### SERVICES数据结构扩展 +```typescript +export const SERVICES = [ + { + id: 'software', + title: '软件开发', + description: '...', + icon: 'Code', + // 新增字段 + content: { + scope: '提供定制化软件开发服务,包括Web应用、移动应用、企业管理系统等', + process: [ + { phase: '需求分析', description: '深入了解业务需求,制定详细需求文档' }, + { phase: '系统设计', description: '设计系统架构、数据库结构、接口规范' }, + { phase: '开发实施', description: '采用敏捷开发方法,快速迭代交付' }, + { phase: '测试验收', description: '全面测试,确保系统质量和稳定性' }, + { phase: '部署上线', description: '协助部署,提供运维支持' } + ], + deliverables: ['源代码', '技术文档', '用户手册', '培训服务'] + }, + advantages: [ + { label: '技术实力', value: '10年+行业经验' }, + { label: '团队规模', value: '50+专业开发人员' }, + { label: '成功案例', value: '100+项目交付' }, + { label: '客户满意度', value: '98%' } + ] + }, + // ... +] +``` + +## 实施计划 + +### 阶段一:数据准备 +- [ ] 扩展 `PRODUCTS` 数据结构,添加详细内容 +- [ ] 扩展 `SERVICES` 数据结构,添加详细内容 +- [ ] 创建产品详情页面的静态数据 + +### 阶段二:页面开发 +- [ ] 创建产品详情页面 `/products/[id]` +- [ ] 创建服务详情页面 `/services/[id]` +- [ ] 创建隐私政策页面 `/privacy` +- [ ] 创建服务条款页面 `/terms` + +### 阶段三:功能集成 +- [ ] 安装Resend SDK和Zod +- [ ] 创建联系表单API路由 `/api/contact` +- [ ] 实现邮件发送功能 +- [ ] 添加表单验证和安全措施 +- [ ] 更新联系表单提交逻辑 + +### 阶段四:测试和优化 +- [ ] 测试所有新页面的功能 +- [ ] 测试邮件发送功能 +- [ ] 测试表单验证和安全措施 +- [ ] 优化页面性能和用户体验 +- [ ] 检查响应式设计 + +### 阶段五:部署和上线 +- [ ] 配置生产环境的环境变量 +- [ ] 构建生产版本 +- [ ] 部署到生产环境 +- [ ] 验证所有功能正常运行 + +## 验收标准 + +### 功能验收 +- [ ] 产品详情页面正常显示所有内容模块 +- [ ] 服务详情页面正常显示所有内容模块 +- [ ] 隐私政策和服务条款页面内容完整 +- [ ] 联系表单能够成功发送邮件 +- [ ] 表单验证和安全措施正常工作 + +### 质量验收 +- [ ] 所有页面通过ESLint检查 +- [ ] 所有页面通过TypeScript类型检查 +- [ ] 响应式设计在各种设备上正常显示 +- [ ] 页面加载性能符合要求 +- [ ] SEO优化符合最佳实践 + +## 风险和注意事项 + +### 技术风险 +- **邮件服务限制**: Resend免费额度为每月3000封,如超出需要升级或更换服务 +- **API密钥安全**: 需要妥善保管API密钥,不要提交到代码仓库 +- **表单滥用**: 需要实施频率限制和验证码,防止恶意提交 + +### 法律风险 +- **隐私政策合规**: 需要确保隐私政策符合最新的法律法规要求 +- **数据保护**: 需要妥善处理用户提交的个人信息,符合个人信息保护法要求 +- **条款更新**: 需要定期更新隐私政策和服务条款,确保内容准确 + +### 运维风险 +- **邮件服务稳定性**: 需要监控邮件服务的可用性和发送成功率 +- **表单数据备份**: 建议保存表单提交记录,便于后续分析和追溯 +- **用户反馈**: 需要及时处理用户反馈,优化用户体验 + +## 后续优化方向 + +1. **产品详情页面** + - 添加产品视频演示 + - 添加在线试用功能 + - 添加产品定价信息 + - 添加常见问题(FAQ)模块 + +2. **服务详情页面** + - 添加服务案例展示 + - 添加服务定价信息 + - 添加在线预约功能 + - 添加服务评价和推荐 + +3. **联系表单** + - 添加文件上传功能 + - 添加智能客服集成 + - 添加在线聊天功能 + - 添加表单数据分析 + +4. **法律页面** + - 添加Cookie同意管理 + - 添加数据导出功能 + - 添加账户删除功能 + - 添加隐私设置页面 + +## 参考资料 + +- [Next.js App Router文档](https://nextjs.org/docs/app) +- [Resend文档](https://resend.com/docs) +- [Zod文档](https://zod.dev) +- [个人信息保护法](http://www.npc.gov.cn/npc/c30834/202108/a8c4e3672c74491a80b53a172bb753fe.shtml) +- [网络安全法](http://www.npc.gov.cn/npc/c30834/201611/6c5a468d8c3f4e8f9d5d5e5e5e5e5e5e.shtml) diff --git a/docs/plans/2026-02-26-website-feature-completion-implementation.md b/docs/plans/2026-02-26-website-feature-completion-implementation.md new file mode 100644 index 0000000..ff5a14c --- /dev/null +++ b/docs/plans/2026-02-26-website-feature-completion-implementation.md @@ -0,0 +1,1420 @@ +# 网站功能补全实施计划 + +> **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: 验证数据结构** + +```bash +npm run dev +``` + +Expected: 开发服务器启动,没有TypeScript错误。 + +**Step 5: Commit** + +```bash +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: 验证数据结构** + +```bash +npm run dev +``` + +Expected: 开发服务器启动,没有TypeScript错误。 + +**Step 5: Commit** + +```bash +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: 创建产品详情页面组件** + +```typescript +'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 ( +
+
+
+ {/* 返回按钮 */} + + + {/* 产品标题 */} +
+ {product.category} +

{product.title}

+

{product.fullDescription}

+
+ + {/* 功能特性 */} + + + 核心功能 + + +
+ {product.features.map((feature, idx) => ( +
+ + {feature} +
+ ))} +
+
+
+ + {/* 技术架构 */} + + + 技术架构 + + +
+
+

+ + 前端技术 +

+
+ {product.technicalArchitecture.frontend.map((tech, idx) => ( + {tech} + ))} +
+
+
+

+ + 后端技术 +

+
+ {product.technicalArchitecture.backend.map((tech, idx) => ( + {tech} + ))} +
+
+
+

+ + 数据库 +

+
+ {product.technicalArchitecture.database.map((tech, idx) => ( + {tech} + ))} +
+
+
+

+ + 部署方式 +

+
+ {product.technicalArchitecture.deployment.map((tech, idx) => ( + {tech} + ))} +
+
+
+
+
+ + {/* 应用场景 */} + + + 应用场景 + + + {product.scenarios.map((scenario, idx) => ( +
+

{scenario.title}

+

{scenario.description}

+
+ ))} +
+
+ + {/* 客户案例 */} + + + 客户案例 + + + {product.cases.map((caseItem, idx) => ( +
+
+ {caseItem.industry} +
+

{caseItem.client}

+
+ {caseItem.results.map((result, resIdx) => ( +
+
{result.value}
+
{result.label}
+
+ ))} +
+
+ ))} +
+
+ + {/* 底部CTA */} +
+ +
+
+
+
+ ); +} +``` + +**Step 2: 验证页面** + +```bash +npm run dev +``` + +Expected: 访问 `/products/erp` 能正常显示产品详情。 + +**Step 3: Commit** + +```bash +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: 创建服务详情页面组件** + +```typescript +'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 ( +
+
+
+ {/* 返回按钮 */} + + + {/* 服务标题 */} +
+

{service.title}

+

{service.fullDescription}

+
+ + {/* 服务内容 */} + + + 服务内容 + + +
+

服务范围

+

{service.content.scope}

+
+ +
+

服务流程

+
+ {service.content.process.map((phase, idx) => ( +
+
+ {idx + 1} +
+
+

{phase.phase}

+

{phase.description}

+
+
+ ))} +
+
+ +
+

交付成果

+
+ {service.content.deliverables.map((item, idx) => ( + {item} + ))} +
+
+
+
+ + {/* 服务优势 */} + + + 服务优势 + + +
+ {service.advantages.map((advantage, idx) => ( +
+ +
+
{advantage.label}
+
{advantage.value}
+
+
+ ))} +
+
+
+ + {/* 相关服务 */} + {service.relatedServices && service.relatedServices.length > 0 && ( + + + 相关服务 + + +
+ {service.relatedServices.map((relatedId, idx) => { + const relatedService = SERVICES.find(s => s.id === relatedId); + if (!relatedService) return null; + return ( + + ); + })} +
+
+
+ )} + + {/* 底部CTA */} +
+ +
+
+
+
+ ); +} +``` + +**Step 2: 验证页面** + +```bash +npm run dev +``` + +Expected: 访问 `/services/software` 能正常显示服务详情。 + +**Step 3: Commit** + +```bash +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: 创建隐私政策页面** + +```typescript +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 ( +
+
+
+

隐私政策

+

最后更新日期: 2026年2月26日

+
+ + + + {/* 引言 */} +
+

1. 引言

+

+ 欢迎访问四川睿新致远科技有限公司(以下简称"我们"或"公司")的官方网站。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的个人信息,以及您对个人信息所享有的权利。 +

+

+ 本隐私政策适用于您访问和使用我们网站时所涉及的个人信息处理活动。通过访问和使用我们的网站,您同意本隐私政策的内容。如果您不同意本隐私政策,请立即停止使用我们的网站。 +

+
+ + {/* 信息收集 */} +
+

2. 信息收集

+

+ 我们在您使用网站服务时,可能收集以下类型的个人信息: +

+ +
+
+

2.1 您主动提供的信息

+

+ 当您填写联系表单、发送邮件、订阅我们的服务或参与我们的活动时,我们可能会收集您的以下信息: +

+
    +
  • 姓名
  • +
  • 电子邮件地址
  • +
  • 电话号码
  • +
  • 公司名称
  • +
  • 职位
  • +
  • 其他您主动提供的信息
  • +
+
+ +
+

2.2 自动收集的信息

+

+ 当您访问我们的网站时,我们可能会自动收集以下信息: +

+
    +
  • IP地址
  • +
  • 浏览器类型和版本
  • +
  • 操作系统类型和版本
  • +
  • 访问时间、页面浏览记录
  • +
  • 设备标识符
  • +
  • Cookie信息
  • +
+
+
+
+ + {/* 信息使用 */} +
+

3. 信息使用

+

+ 我们将收集的信息用于以下目的: +

+
    +
  • 为您提供网站服务和功能
  • +
  • 处理您的咨询、反馈和请求
  • +
  • 与您沟通,包括发送通知和回复您的问题
  • +
  • 改进我们的网站和服务
  • +
  • 进行数据分析和研究
  • +
  • 保护网站的安全和防止欺诈
  • +
  • 遵守适用的法律法规
  • +
+
+ + {/* 信息共享 */} +
+

4. 信息共享

+

+ 我们不会出售或出租您的个人信息。在以下情况下,我们可能会共享您的信息: +

+
    +
  • 获得您的明确同意
  • +
  • 为履行法律法规规定的义务
  • +
  • 为保护我们的合法权益或他人权益
  • +
  • 与我们的关联公司、合作伙伴共享(仅限必要范围)
  • +
  • 与第三方服务提供商共享(如邮件服务、分析服务)
  • +
+
+ + {/* 信息存储 */} +
+

5. 信息存储

+

+ 我们将采取合理措施保护您的个人信息,防止信息丢失、泄露或被未经授权访问。 +

+

+ 我们将根据以下原则确定个人信息的存储期限: +

+
    +
  • 为实现本隐私政策所述的目的所必需的期限
  • +
  • 为遵守适用的法律法规所必需的期限
  • +
  • 为解决争议、履行法律责任所必需的期限
  • +
+
+ + {/* 用户权利 */} +
+

6. 用户权利

+

+ 您对您的个人信息享有以下权利: +

+
    +
  • 访问权:您可以请求访问您的个人信息
  • +
  • 更正权:您可以请求更正您的个人信息
  • +
  • 删除权:您可以请求删除您的个人信息
  • +
  • 限制处理权:您可以请求限制对您的个人信息的处理
  • +
  • 数据可携权:您可以请求获取您的个人信息副本
  • +
  • 撤回同意权:您可以随时撤回对个人信息处理的同意
  • +
+

+ 如您行使上述权利,请通过本隐私政策第10节提供的联系方式与我们联系。 +

+
+ + {/* Cookie使用 */} +
+

7. Cookie使用

+

+ 我们网站使用Cookie和其他类似技术来提供服务和改进用户体验。Cookie是存储在您设备上的小数据文件,用于识别您的浏览器或设备。 +

+

+ 您可以通过浏览器设置拒绝或管理Cookie。但请注意,如果您禁用Cookie,可能会影响网站某些功能的正常使用。 +

+
+ + {/* 未成年人保护 */} +
+

8. 未成年人保护

+

+ 我们重视对未成年人个人信息的保护。我们的网站和服务不面向14岁以下的儿童。如果我们发现我们收集了14岁以下儿童的个人信息,我们将尽快删除相关信息。如果您是未成年人的监护人,并且认为我们收集了您监护的儿童的个人信息,请与我们联系。 +

+
+ + {/* 政策更新 */} +
+

9. 政策更新

+

+ 我们可能会不定期更新本隐私政策。更新后的隐私政策将发布在本页面上,并注明更新日期。我们建议您定期查看本隐私政策,以了解我们如何保护您的个人信息。如果您不同意更新后的隐私政策,请停止使用我们的网站。 +

+
+ + {/* 联系方式 */} +
+

10. 联系方式

+

+ 如您对本隐私政策有任何疑问或意见,或需要行使您的个人信息权利,请通过以下方式与我们联系: +

+
+

+ 公司名称: {COMPANY_INFO.name}
+ 地址: {COMPANY_INFO.address}
+ 电话: {COMPANY_INFO.phone}
+ 邮箱: {COMPANY_INFO.email} +

+
+
+
+
+
+
+ ); +} +``` + +**Step 2: 验证页面** + +```bash +npm run dev +``` + +Expected: 访问 `/privacy` 能正常显示隐私政策页面。 + +**Step 3: Commit** + +```bash +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: 创建服务条款页面** + +```typescript +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 ( +
+
+
+

服务条款

+

最后更新日期: 2026年2月26日

+
+ + + + {/* 引言 */} +
+

1. 引言

+

+ 欢迎访问四川睿新致远科技有限公司(以下简称"我们"或"公司")的官方网站。本服务条款旨在向您说明您使用我们网站服务时应遵守的规则和条款。 +

+

+ 本服务条款适用于您访问和使用我们网站时所涉及的所有行为。通过访问和使用我们的网站,您同意本服务条款的内容。如果您不同意本服务条款,请立即停止使用我们的网站。 +

+
+ + {/* 服务内容 */} +
+

2. 服务内容

+

+ 我们为用户提供以下网站服务: +

+
    +
  • 公司信息展示
  • +
  • 产品和服务介绍
  • +
  • 新闻动态和行业资讯
  • +
  • 在线联系和咨询
  • +
  • 其他我们不时提供的服务
  • +
+

+ 我们保留随时修改、中断或终止全部或部分网站服务的权利,无需事先通知用户。 +

+
+ + {/* 用户义务 */} +
+

3. 用户义务

+

+ 您在使用我们的网站服务时,应当遵守以下义务: +

+
    +
  • 遵守中华人民共和国相关法律法规和政策
  • +
  • 遵守本服务条款的各项条款
  • +
  • 不得侵犯他人的合法权益(包括知识产权、隐私权等)
  • +
  • 不得发布违法、有害、虚假、暴力、色情等内容
  • +
  • 不得干扰或破坏网站服务,不得使用非我们授权的工具访问网站
  • +
  • 不得以任何方式获取网站未授权访问的数据或信息
  • +
  • 如实提供个人资料,并及时更新资料
  • +
+
+ + {/* 知识产权 */} +
+

4. 知识产权

+

+ 网站上所有内容(包括但不限于文字、图片、音频、视频、软件、代码等)的知识产权均归我们或相关权利人所有。 +

+

+ 未经我们或相关权利人的书面许可,您不得以任何形式复制、转载、引用、修改、创建衍生作品或用于商业用途。但您可为个人非商业目的下载、打印或存储少量内容。 +

+
+ + {/* 免责条款 */} +
+

5. 免责条款

+

+ 我们尽力确保网站的准确性和可靠性,但不对网站信息的准确性、完整性、及时性、可靠性作出任何保证。 +

+

+ 在任何情况下,我们都不对因使用或无法使用网站服务而导致的任何直接、间接、偶然、特殊或惩罚性损害承担责任,包括但不限于利润损失、数据丢失、业务中断等。 +

+
+ + {/* 服务变更 */} +
+

6. 服务变更

+

+ 我们保留随时修改、中断或终止全部或部分网站服务的权利,包括但不限于修改网站内容、功能、服务范围等。 +

+

+ 我们将在网站上发布变更通知,但您理解并同意,我们无需对任何因服务变更导致的损失承担责任。 +

+
+ + {/* 争议解决 */} +
+

7. 争议解决

+

+ 本服务条款的订立、执行、解释及争议解决,均适用中华人民共和国法律。 +

+

+ 如因本服务条款产生争议,双方应友好协商解决;协商不成的,任何一方均有权向有管辖权的人民法院提起诉讼。 +

+
+ + {/* 条款更新 */} +
+

8. 条款更新

+

+ 我们可能会不定期更新本服务条款。更新后的服务条款将发布在本页面上,并注明更新日期。我们建议您定期查看本服务条款,以了解您使用网站服务时应遵守的规则。如果您不同意更新后的服务条款,请停止使用我们的网站。 +

+
+ + {/* 联系方式 */} +
+

9. 联系方式

+

+ 如您对本服务条款有任何疑问或意见,请通过以下方式与我们联系: +

+
+

+ 公司名称: {COMPANY_INFO.name}
+ 地址: {COMPANY_INFO.address}
+ 电话: {COMPANY_INFO.phone}
+ 邮箱: {COMPANY_INFO.email} +

+
+
+
+
+
+
+ ); +} +``` + +**Step 2: 验证页面** + +```bash +npm run dev +``` + +Expected: 访问 `/terms` 能正常显示服务条款页面。 + +**Step 3: Commit** + +```bash +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** + +```bash +npm install resend zod +``` + +Expected: Resend和Zod添加到package.json的dependencies中。 + +**Step 2: 验证安装** + +```bash +npm list resend zod +``` + +Expected: 显示Resend和Zod的版本号。 + +**Step 3: Commit** + +```bash +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路由** + +```typescript +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; + +// 初始化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 ', + to: [process.env.CONTACT_EMAIL || 'contact@novalon.cn'], + subject: `[${subject}] ${name}`, + html: ` +
+

新联系表单提交

+ +
+

姓名: ${name}

+ ${phone ? `

电话: ${phone}

` : ''} +

邮箱: ${email}

+

主题: ${subject}

+

消息内容:

+
${message}
+
+ +

此邮件由网站联系表单自动发送,请勿直接回复。

+
+ `, + }); + + // 发送确认邮件(可选) + if (response.data) { + try { + await resend.emails.send({ + from: process.env.FROM_EMAIL || 'No reply ', + to: [email], + subject: '感谢您的联系 - 确认邮件', + html: ` +
+

感谢您的联系!

+ +

您好 ${name},

+ +

+ 我们已收到您的消息,主题为"${subject}"。我们的团队会尽快查看并回复您。 +

+ +

+ 如果您有任何其他问题,欢迎随时联系我们。 +

+ +
+

您提交的信息:

+

主题: ${subject}

+

消息: ${message.substring(0, 200)}${message.length > 200 ? '...' : ''}

+
+ +

此邮件由网站自动发送,请勿直接回复。

+
+ `, + }); + } 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路由** + +```bash +npm run dev +``` + +Expected: API路由创建成功,没有TypeScript错误。 + +**Step 3: Commit** + +```bash +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: 更新联系表单提交逻辑** + +```typescript +// 替换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: 添加错误显示** + +在表单中添加错误显示: + +```typescript +{error && ( +
+

{error}

+
+)} +``` + +**Step 4: 验证表单** + +```bash +npm run dev +``` + +Expected: 联系表单能够成功提交并发送邮件。 + +**Step 5: Commit** + +```bash +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** + +```bash +npm run lint +``` + +Expected: 所有文件通过ESLint检查,没有错误。 + +**Step 2: 修复ESLint错误** + +根据ESLint输出,修复所有错误。 + +**Step 3: 运行TypeScript类型检查** + +```bash +npx tsc --noEmit +``` + +Expected: 没有TypeScript类型错误。 + +**Step 4: Commit** + +```bash +git add . +git commit -m "fix: fix linting and type errors" +``` + +--- + +### Task 11: 运行开发服务器测试 + +**Files:** +- All new and modified files + +**Step 1: 启动开发服务器** + +```bash +npm run dev +``` + +Expected: 开发服务器启动成功,没有错误。 + +**Step 2: 测试所有页面** + +- 访问 `/products/erp` - 测试产品详情页面 +- 访问 `/services/software` - 测试服务详情页面 +- 访问 `/privacy` - 测试隐私政策页面 +- 访问 `/terms` - 测试服务条款页面 +- 访问 `/contact` - 测试联系表单 + +Expected: 所有页面正常显示,功能正常。 + +**Step 3: Commit** + +```bash +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** + +```bash +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`文件** + +```bash +cp .env.example .env.local +``` + +**Step 2: 配置环境变量** + +在`.env.local`中添加: + +```bash +# Resend API配置 +RESEND_API_KEY=re_123456789_your_actual_api_key +FROM_EMAIL=No reply +CONTACT_EMAIL=contact@novalon.cn +``` + +**Step 3: 验证环境变量** + +```bash +npm run dev +``` + +Expected: 开发服务器启动成功,邮件发送功能正常。 + +**Step 4: Commit** + +```bash +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: 构建生产版本** + +```bash +npm run build +``` + +Expected: 构建成功,没有错误。 + +**Step 2: 验证构建输出** + +检查`dist/`目录,确保所有文件都已生成。 + +**Step 3: Commit** + +```bash +git add . +git commit -m "build: create production build" +``` + +--- + +### Task 15: 部署到生产环境 + +**Files:** +- All files + +**Step 1: 部署** + +根据您的部署平台,部署生产版本。 + +**Step 2: 验证生产环境** + +- 访问生产环境的URL +- 测试所有页面 +- 测试联系表单 + +Expected: 生产环境正常运行,所有功能正常。 + +**Step 3: Commit** + +```bash +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. 建议添加表单提交日志,便于后续分析和追溯 diff --git a/package-lock.json b/package-lock.json index ca97d02..9b98c0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3", + "resend": "^6.9.2", "tailwind-merge": "^3.4.0", "three": "^0.183.1", "zod": "^4.3.6" @@ -1761,6 +1762,12 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3039,6 +3046,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -3846,6 +3859,12 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/postal-mime": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.3.tgz", + "integrity": "sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==", + "license": "MIT-0" + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3985,6 +4004,27 @@ } } }, + "node_modules/resend": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.2.tgz", + "integrity": "sha512-uIM6CQ08tS+hTCRuKBFbOBvHIGaEhqZe8s4FOgqsVXSbQLAhmNWpmUhG3UAtRnmcwTWFUqnHa/+Vux8YGPyDBA==", + "license": "MIT", + "dependencies": { + "postal-mime": "2.7.3", + "svix": "1.84.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -4102,6 +4142,16 @@ "node": ">=0.10.0" } }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -4131,6 +4181,16 @@ "integrity": "sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==", "license": "MIT" }, + "node_modules/svix": { + "version": "1.84.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.84.1.tgz", + "integrity": "sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==", + "license": "MIT", + "dependencies": { + "standardwebhooks": "1.0.0", + "uuid": "^10.0.0" + } + }, "node_modules/tailwind-merge": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", @@ -4279,6 +4339,19 @@ "base64-arraybuffer": "^1.0.2" } }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 0bff79f..b4bd430 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3", + "resend": "^6.9.2", "tailwind-merge": "^3.4.0", "three": "^0.183.1", "zod": "^4.3.6" diff --git a/src/app/(marketing)/contact/page.tsx b/src/app/(marketing)/contact/page.tsx index 1d93187..02360f7 100644 --- a/src/app/(marketing)/contact/page.tsx +++ b/src/app/(marketing)/contact/page.tsx @@ -13,13 +13,36 @@ export default function ContactPage() { const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); - async function handleSubmit(_formData: FormData) { + async function handleSubmit(formData: FormData) { setIsSubmitting(true); - await new Promise(resolve => setTimeout(resolve, 1500)); - - setIsSubmitting(false); - setIsSubmitted(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'), + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || '发送失败,请稍后再试'); + } + + setIsSubmitted(true); + } catch (error) { + console.error('Form submission error:', error); + alert(error instanceof Error ? error.message : '发送失败,请稍后再试'); + } finally { + setIsSubmitting(false); + } } return ( diff --git a/src/app/(marketing)/products/[id]/page.tsx b/src/app/(marketing)/products/[id]/page.tsx index 72046a6..5145ef2 100644 --- a/src/app/(marketing)/products/[id]/page.tsx +++ b/src/app/(marketing)/products/[id]/page.tsx @@ -3,7 +3,7 @@ import { PRODUCTS, COMPANY_INFO } from '@/lib/constants'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { ArrowRight, Check, TrendingUp, Code, Cloud, Database, Server, Briefcase, Target, Users, Award } from 'lucide-react'; +import { ArrowRight, Check, TrendingUp, Code, Cloud, Database, Server, Target, Users } from 'lucide-react'; interface ProductDetailPageProps { params: Promise<{ id: string }>; @@ -22,7 +22,7 @@ export async function generateMetadata({ params }: ProductDetailPageProps) { return { title: `${product.title} - ${COMPANY_INFO.name}`, - description: product.fullDescription || product.description, + description: product.fullDescription, }; } diff --git a/src/app/(marketing)/services/[id]/page.tsx b/src/app/(marketing)/services/[id]/page.tsx index 073439a..0e11e77 100644 --- a/src/app/(marketing)/services/[id]/page.tsx +++ b/src/app/(marketing)/services/[id]/page.tsx @@ -3,7 +3,7 @@ import { SERVICES, COMPANY_INFO } from '@/lib/constants'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { ArrowRight, Check, Target, Users, Award, Briefcase, CheckCircle, FileText, Shield, TrendingUp } from 'lucide-react'; +import { ArrowRight, CheckCircle, FileText, Award } from 'lucide-react'; interface ServiceDetailPageProps { params: Promise<{ id: string }>; @@ -22,7 +22,7 @@ export async function generateMetadata({ params }: ServiceDetailPageProps) { return { title: `${service.title} - ${COMPANY_INFO.name}`, - description: service.fullDescription || service.description, + description: service.fullDescription, }; } diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts new file mode 100644 index 0000000..44c7dae --- /dev/null +++ b/src/app/api/contact/route.ts @@ -0,0 +1,107 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { Resend } from 'resend'; +import { z } from 'zod'; + +const contactSchema = z.object({ + name: z.string().min(1, '姓名不能为空').max(50, '姓名不能超过50个字符'), + phone: z.string().optional(), + email: z.string().min(1, '邮箱不能为空').email('邮箱格式不正确').max(100, '邮箱不能超过100个字符'), + subject: z.string().min(1, '主题不能为空').max(100, '主题不能超过100个字符'), + message: z.string().min(1, '消息内容不能为空').max(1000, '消息内容不能超过1000个字符'), +}); + +const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1小时 +const RATE_LIMIT_MAX_REQUESTS = 10; + +interface RateLimitRecord { + count: number; + resetTime: number; +} + +const rateLimitStore: Record = {}; + +function checkRateLimit(ip: string): { allowed: boolean; remaining: number } { + const now = Date.now(); + const record = rateLimitStore[ip]; + + if (!record || now > record.resetTime) { + rateLimitStore[ip] = { + count: 1, + resetTime: now + RATE_LIMIT_WINDOW, + }; + return { allowed: true, remaining: RATE_LIMIT_MAX_REQUESTS - 1 }; + } + + record.count += 1; + const remaining = Math.max(0, RATE_LIMIT_MAX_REQUESTS - record.count); + + if (record.count > RATE_LIMIT_MAX_REQUESTS) { + return { allowed: false, remaining: 0 }; + } + + return { allowed: true, remaining }; +} + +export async function POST(request: NextRequest) { + const clientIp = request.headers.get('x-forwarded-for') || 'unknown'; + + // 检查速率限制 + const { allowed, remaining } = checkRateLimit(clientIp); + if (!allowed) { + return NextResponse.json( + { error: '请求过于频繁,请稍后再试' }, + { status: 429, headers: { 'X-RateLimit-Remaining': '0' } } + ); + } + + try { + const body = await request.json(); + const validatedData = contactSchema.parse(body); + + const { name, phone, email, subject, message } = validatedData; + + const resend = new Resend(process.env.RESEND_API_KEY); + + await resend.emails.send({ + from: process.env.FROM_EMAIL || 'No reply ', + to: [process.env.CONTACT_EMAIL || 'contact@novalon.cn'], + subject: `[${subject}] ${name}`, + html: ` +
+

新消息通知

+
+

姓名:${name}

+ ${phone ? `

电话:${phone}

` : ''} +

邮箱:${email}

+

主题:${subject}

+

消息内容:

+
${message}
+
+

此邮件由系统自动发送,请勿直接回复。

+
+ `, + }); + + return NextResponse.json( + { success: true, remaining }, + { status: 200, headers: { 'X-RateLimit-Remaining': remaining.toString() } } + ); + } catch (error) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { error: '数据验证失败', details: error.issues }, + { status: 400 } + ); + } + + console.error('Contact form error:', error); + return NextResponse.json( + { error: '发送失败,请稍后再试' }, + { status: 500 } + ); + } +} + +export async function OPTIONS() { + return NextResponse.json({}, { status: 200 }); +}