feat: 实现联系表单提交与邮件通知功能
添加联系表单提交接口和邮件通知功能,支持从环境变量读取 SMTP 配置 重构 SEO 配置到 site.json,新增 robots.txt 和 sitemap.xml 生成 更新公司电话并添加 PM2 生产运行配置
This commit is contained in:
116
lib/contact-mailer.ts
Normal file
116
lib/contact-mailer.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import nodemailer from "nodemailer"
|
||||
import type { ContactFormData } from "@/lib/contact-form"
|
||||
import type { ContactMailerConfig } from "@/lib/contact-mailer-config"
|
||||
|
||||
type ContactNotificationMeta = {
|
||||
submittedAt: string
|
||||
sourceUrl: string
|
||||
ipAddress: string
|
||||
userAgent: string
|
||||
}
|
||||
|
||||
function escapeHtml(value: string) {
|
||||
return value
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("\"", """)
|
||||
.replaceAll("'", "'")
|
||||
}
|
||||
|
||||
function formatOptionalField(value: string) {
|
||||
return value || "未填写"
|
||||
}
|
||||
|
||||
export async function sendContactNotification({
|
||||
config,
|
||||
payload,
|
||||
meta,
|
||||
}: {
|
||||
config: ContactMailerConfig
|
||||
payload: ContactFormData
|
||||
meta: ContactNotificationMeta
|
||||
}) {
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: config.smtp.host,
|
||||
port: config.smtp.port,
|
||||
secure: config.smtp.secure,
|
||||
auth: {
|
||||
user: config.smtp.auth.user,
|
||||
pass: config.smtp.auth.pass,
|
||||
},
|
||||
})
|
||||
|
||||
const subject = `${config.notification.subjectPrefix} - ${payload.company} / ${payload.name}`
|
||||
const text = [
|
||||
"官网获取初步方案表单有新提交。",
|
||||
"",
|
||||
`姓名:${payload.name}`,
|
||||
`公司 / 品牌:${payload.company}`,
|
||||
`电话:${payload.phone}`,
|
||||
`门店规模:${formatOptionalField(payload.scale)}`,
|
||||
`需求描述:${formatOptionalField(payload.message)}`,
|
||||
"",
|
||||
`提交时间:${meta.submittedAt}`,
|
||||
`来源页面:${meta.sourceUrl}`,
|
||||
`来源 IP:${meta.ipAddress}`,
|
||||
`User-Agent:${meta.userAgent}`,
|
||||
].join("\n")
|
||||
|
||||
const html = `
|
||||
<div style="font-family: Arial, sans-serif; color: #111827; line-height: 1.7;">
|
||||
<h2 style="margin: 0 0 16px;">官网获取初步方案表单有新提交</h2>
|
||||
<table style="border-collapse: collapse; width: 100%; max-width: 720px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; width: 160px; background: #f9fafb;">姓名</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(payload.name)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">公司 / 品牌</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(payload.company)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">电话</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(payload.phone)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">门店规模</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(formatOptionalField(payload.scale))}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">需求描述</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; white-space: pre-wrap;">${escapeHtml(formatOptionalField(payload.message))}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">提交时间</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(meta.submittedAt)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">来源页面</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(meta.sourceUrl)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">来源 IP</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(meta.ipAddress)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb; background: #f9fafb;">User-Agent</td>
|
||||
<td style="padding: 8px 12px; border: 1px solid #e5e7eb;">${escapeHtml(meta.userAgent)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
|
||||
await transporter.sendMail({
|
||||
from: {
|
||||
name: config.smtp.from.name,
|
||||
address: config.smtp.from.email,
|
||||
},
|
||||
to: config.notification.to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user