@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* POST /api/contact
|
||||
* 联系表单提交接口(通用留言)
|
||||
*
|
||||
* 请求体:
|
||||
* {
|
||||
* name: string // 姓名(必填)
|
||||
* phone: string // 手机号(必填)
|
||||
* subject?: string // 咨询主题
|
||||
* message: string // 留言内容(必填)
|
||||
* }
|
||||
*/
|
||||
|
||||
import { checkRateLimit } from '../utils/rateLimit'
|
||||
import { appendRecord } from '../utils/storage'
|
||||
import { notifyWecom } from '../utils/notify'
|
||||
|
||||
const PHONE_RE = /^1[3-9]\d{9}$/
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// ── 速率限制 ──────────────────────────────────────────
|
||||
const ip = getRequestIP(event, { xForwardedFor: true }) ?? 'unknown'
|
||||
const { allowed, remaining, resetAt } = checkRateLimit(`contact:${ip}`)
|
||||
|
||||
setResponseHeader(event, 'X-RateLimit-Remaining', String(remaining))
|
||||
|
||||
if (!allowed) {
|
||||
const waitMin = Math.ceil((resetAt - Date.now()) / 60000)
|
||||
throw createError({
|
||||
statusCode: 429,
|
||||
message: `提交过于频繁,请 ${waitMin} 分钟后再试`,
|
||||
})
|
||||
}
|
||||
|
||||
// ── 读取请求体 ─────────────────────────────────────────
|
||||
const body = await readBody(event)
|
||||
|
||||
// ── 字段校验 ──────────────────────────────────────────
|
||||
const errors = {}
|
||||
|
||||
if (!body?.name?.trim()) {
|
||||
errors.name = '请填写您的姓名'
|
||||
}
|
||||
|
||||
if (!body?.phone || !PHONE_RE.test(body.phone.trim())) {
|
||||
errors.phone = '请填写正确的手机号码'
|
||||
}
|
||||
|
||||
if (!body?.message?.trim() || body.message.trim().length < 5) {
|
||||
errors.message = '请填写留言内容(至少5个字)'
|
||||
}
|
||||
|
||||
if (body?.message?.trim().length > 1000) {
|
||||
errors.message = '留言内容不超过1000字'
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
throw createError({
|
||||
statusCode: 422,
|
||||
data: { errors },
|
||||
message: '提交内容有误,请检查后重试',
|
||||
})
|
||||
}
|
||||
|
||||
// ── 构建记录 ──────────────────────────────────────────
|
||||
const record = {
|
||||
id: `CT${Date.now()}`,
|
||||
createdAt: new Date().toISOString(),
|
||||
name: body.name.trim(),
|
||||
phone: body.phone.trim(),
|
||||
subject: body.subject?.trim() ?? '',
|
||||
message: body.message.trim(),
|
||||
ip,
|
||||
source: 'contact-form',
|
||||
}
|
||||
|
||||
// ── 持久化 ────────────────────────────────────────────
|
||||
await appendRecord('contact.json', record)
|
||||
|
||||
// ── 企业微信通知(配置 WECOM_WEBHOOK_KEY 后生效)───────
|
||||
await notifyWecom('📩 新联系留言 - 呼籁旅行', {
|
||||
'编号': record.id,
|
||||
'姓名': record.name,
|
||||
'手机': record.phone,
|
||||
'咨询主题': record.subject || '(未填)',
|
||||
'留言内容': record.message,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: record.id,
|
||||
message: '留言已收到,我们会尽快与您联系',
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* POST /api/customize
|
||||
* 定制表单提交接口
|
||||
*
|
||||
* 请求体:
|
||||
* {
|
||||
* name: string // 姓名(必填)
|
||||
* phone: string // 手机号(必填)
|
||||
* wechat?: string // 微信号(选填)
|
||||
* adults: number // 成人数(必填)
|
||||
* children: number // 儿童数
|
||||
* childAges: number[] // 儿童年龄列表
|
||||
* date?: string // 出行日期 YYYY-MM-DD
|
||||
* duration?: string // 出行天数
|
||||
* activities: string[]// 特殊需求标签
|
||||
* budget?: string // 预算范围
|
||||
* remarks?: string // 补充说明
|
||||
* }
|
||||
*/
|
||||
|
||||
import { checkRateLimit } from '../utils/rateLimit'
|
||||
import { appendRecord } from '../utils/storage'
|
||||
import { notifyWecom } from '../utils/notify'
|
||||
|
||||
// 手机号正则
|
||||
const PHONE_RE = /^1[3-9]\d{9}$/
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// ── 速率限制 ──────────────────────────────────────────
|
||||
const ip = getRequestIP(event, { xForwardedFor: true }) ?? 'unknown'
|
||||
const { allowed, remaining, resetAt } = checkRateLimit(`customize:${ip}`)
|
||||
|
||||
setResponseHeader(event, 'X-RateLimit-Remaining', String(remaining))
|
||||
|
||||
if (!allowed) {
|
||||
const waitMin = Math.ceil((resetAt - Date.now()) / 60000)
|
||||
throw createError({
|
||||
statusCode: 429,
|
||||
message: `提交过于频繁,请 ${waitMin} 分钟后再试`,
|
||||
})
|
||||
}
|
||||
|
||||
// ── 读取请求体 ─────────────────────────────────────────
|
||||
const body = await readBody(event)
|
||||
|
||||
// ── 字段校验 ──────────────────────────────────────────
|
||||
const errors = {}
|
||||
|
||||
if (!body?.name?.trim()) {
|
||||
errors.name = '请填写您的姓名'
|
||||
}
|
||||
|
||||
if (!body?.phone || !PHONE_RE.test(body.phone.trim())) {
|
||||
errors.phone = '请填写正确的手机号码(11位大陆手机号)'
|
||||
}
|
||||
|
||||
if (!Number.isInteger(body?.adults) || body.adults < 1 || body.adults > 20) {
|
||||
errors.adults = '成人人数不合法'
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
throw createError({
|
||||
statusCode: 422,
|
||||
data: { errors },
|
||||
message: '提交内容有误,请检查后重试',
|
||||
})
|
||||
}
|
||||
|
||||
// ── 构建记录 ──────────────────────────────────────────
|
||||
const record = {
|
||||
id: `CZ${Date.now()}`,
|
||||
createdAt: new Date().toISOString(),
|
||||
name: body.name.trim(),
|
||||
phone: body.phone.trim(),
|
||||
wechat: body.wechat?.trim() ?? '',
|
||||
adults: body.adults,
|
||||
children: body.children ?? 0,
|
||||
childAges: Array.isArray(body.childAges) ? body.childAges : [],
|
||||
date: body.date ?? '',
|
||||
duration: body.duration ?? '',
|
||||
activities: Array.isArray(body.activities) ? body.activities : [],
|
||||
budget: body.budget ?? '',
|
||||
remarks: body.remarks?.trim() ?? '',
|
||||
ip,
|
||||
source: 'customize-form',
|
||||
}
|
||||
|
||||
// ── 持久化 ────────────────────────────────────────────
|
||||
await appendRecord('customize.json', record)
|
||||
|
||||
// ── 企业微信通知(配置 WECOM_WEBHOOK_KEY 后生效)───────
|
||||
const childInfo = record.children > 0
|
||||
? `成人 ${record.adults} 人、儿童 ${record.children} 人(${record.childAges.join('、')}岁)`
|
||||
: `成人 ${record.adults} 人`
|
||||
|
||||
await notifyWecom('🌿 新定制需求 - 呼籁旅行', {
|
||||
'编号': record.id,
|
||||
'姓名': record.name,
|
||||
'手机': record.phone,
|
||||
'微信': record.wechat || '(同手机号)',
|
||||
'出行人数': childInfo,
|
||||
'出行日期': record.date || '未填写',
|
||||
'出行天数': record.duration || '未选择',
|
||||
'特殊需求': record.activities.length ? record.activities.join('、') : '无',
|
||||
'预算范围': record.budget || '未选择',
|
||||
'补充说明': record.remarks || '无',
|
||||
})
|
||||
|
||||
// ── 返回 ──────────────────────────────────────────────
|
||||
return {
|
||||
success: true,
|
||||
id: record.id,
|
||||
message: '需求已收到,定制师将在2小时内联系您',
|
||||
}
|
||||
})
|
||||
在新工单中引用
屏蔽一个用户