feat: 实现联系表单提交与邮件通知功能
添加联系表单提交接口和邮件通知功能,支持从环境变量读取 SMTP 配置 重构 SEO 配置到 site.json,新增 robots.txt 和 sitemap.xml 生成 更新公司电话并添加 PM2 生产运行配置
This commit is contained in:
79
app/api/contact/route.ts
Normal file
79
app/api/contact/route.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { ZodError } from "zod"
|
||||
import { contactFormSchema } from "@/lib/contact-form"
|
||||
import { sendContactNotification } from "@/lib/contact-mailer"
|
||||
import { ContactMailerConfigError, loadContactMailerConfig } from "@/lib/contact-mailer-config"
|
||||
|
||||
export const runtime = "nodejs"
|
||||
|
||||
function getClientIp(request: Request) {
|
||||
const forwardedFor = request.headers.get("x-forwarded-for")
|
||||
|
||||
if (forwardedFor) {
|
||||
return forwardedFor.split(",")[0]?.trim() || "unknown"
|
||||
}
|
||||
|
||||
return request.headers.get("x-real-ip") ?? "unknown"
|
||||
}
|
||||
|
||||
function formatSubmittedAt() {
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
timeZone: "Asia/Shanghai",
|
||||
dateStyle: "full",
|
||||
timeStyle: "long",
|
||||
}).format(new Date())
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
let requestBody: unknown
|
||||
|
||||
try {
|
||||
requestBody = await request.json()
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: "请求格式不正确,请刷新后重试。" },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = contactFormSchema.parse(requestBody)
|
||||
const config = await loadContactMailerConfig()
|
||||
|
||||
await sendContactNotification({
|
||||
config,
|
||||
payload,
|
||||
meta: {
|
||||
submittedAt: formatSubmittedAt(),
|
||||
sourceUrl: request.headers.get("referer") ?? request.headers.get("origin") ?? new URL(request.url).origin,
|
||||
ipAddress: getClientIp(request),
|
||||
userAgent: request.headers.get("user-agent") ?? "unknown",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ ok: true })
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.issues[0]?.message ?? "提交信息不完整,请检查后重试。" },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (error instanceof ContactMailerConfigError) {
|
||||
console.error(error.message)
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "邮件通知配置不完整,请联系管理员检查 SMTP 设置。" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
console.error("Failed to send contact notification", error)
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: "提交失败,邮件通知未发送成功,请稍后重试。" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import { Analytics } from '@vercel/analytics/next'
|
||||
import { buildSiteMetadata } from '@/lib/seo'
|
||||
import './globals.css'
|
||||
|
||||
const inter = Inter({
|
||||
@@ -9,17 +10,7 @@ const inter = Inter({
|
||||
display: 'swap',
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '连锁门店收银、分账、监管系统 | 智店软件',
|
||||
description: '面向连锁餐饮、零售与加盟品牌,提供门店收银、自动分账、总部监管一体化系统定制与升级服务。兼容现有设备与流程,支持不停业切换。',
|
||||
keywords: '连锁门店系统, 收银系统, 自动分账系统, 总部监管系统, 加盟门店结算, 门店数字化, 智店软件',
|
||||
authors: [{ name: '智店软件' }],
|
||||
openGraph: {
|
||||
title: '连锁门店收银、分账、监管系统 | 智店软件',
|
||||
description: '为连锁餐饮、零售与加盟品牌提供门店收银、自动分账、总部监管一体化系统定制与升级服务。',
|
||||
type: 'website',
|
||||
},
|
||||
}
|
||||
export const metadata: Metadata = buildSiteMetadata()
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
||||
6
app/robots.ts
Normal file
6
app/robots.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
import { buildRobots } from "@/lib/seo"
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return buildRobots()
|
||||
}
|
||||
6
app/sitemap.ts
Normal file
6
app/sitemap.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
import { buildSitemap } from "@/lib/seo"
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return buildSitemap()
|
||||
}
|
||||
Reference in New Issue
Block a user