添加联系表单提交接口和邮件通知功能,支持从环境变量读取 SMTP 配置 重构 SEO 配置到 site.json,新增 robots.txt 和 sitemap.xml 生成 更新公司电话并添加 PM2 生产运行配置
117 lines
4.3 KiB
TypeScript
117 lines
4.3 KiB
TypeScript
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,
|
||
})
|
||
}
|