feat: 实现联系表单提交与邮件通知功能

添加联系表单提交接口和邮件通知功能,支持从环境变量读取 SMTP 配置
重构 SEO 配置到 site.json,新增 robots.txt 和 sitemap.xml 生成
更新公司电话并添加 PM2 生产运行配置
This commit is contained in:
JanYork
2026-03-18 13:20:40 +08:00
parent 59e80bf938
commit e2850586a9
20 changed files with 872 additions and 28 deletions

79
app/api/contact/route.ts Normal file
View 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 }
)
}
}

View File

@@ -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
View 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
View File

@@ -0,0 +1,6 @@
import type { MetadataRoute } from "next"
import { buildSitemap } from "@/lib/seo"
export default function sitemap(): MetadataRoute.Sitemap {
return buildSitemap()
}