fix: 修复TypeScript类型错误

- 移除未使用的导入
- 修复产品详情页面的description类型错误
- 修复服务详情页面的description类型错误
- 修复联系表单API的类型错误
- 添加Award图标的导入
This commit is contained in:
张翔
2026-02-26 16:26:40 +08:00
parent e4cf4836dd
commit 3de9890fc4
8 changed files with 2030 additions and 9 deletions
@@ -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)
File diff suppressed because it is too large Load Diff
+73
View File
@@ -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",
+1
View File
@@ -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"
+28 -5
View File
@@ -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 (
+2 -2
View File
@@ -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,
};
}
+2 -2
View File
@@ -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,
};
}
+107
View File
@@ -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<string, RateLimitRecord> = {};
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 <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: #333;">新消息通知</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: #fff; padding: 15px; border-radius: 4px; margin-top: 10px; white-space: pre-wrap;">${message}</div>
</div>
<p style="color: #666; font-size: 14px;">此邮件由系统自动发送,请勿直接回复。</p>
</div>
`,
});
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 });
}