hulai-website/server/api/customize.post.js
Mimingguang ee078b61bc feat: 后台管理系统完整重构 + 前后台数据联通
- 后台全面引入 Naive UI 组件库,统一 UI 规范
- 前台 usePublicApi 切换到 API 调用(GET /api/content/[key])
- 前后台数据源统一(site_content 表)
- 产品线统一管理(tour/camp/course 三套编辑器)
- 产品数据归一化(itinerary/faq/pricing 格式统一)
- 表单提交 API 改写入 submissions 表
- 新增 submissions 标记已读 API
- site-content PUT 支持 upsert
- 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb)
- 消除 PageHero 类型警告(useSEO reactive 修复)
- 25 个前台页面全部 HTTP 200,展示效果不变

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:48:02 +08:00

117 行
4.3 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* 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 { notifyWecom } from '../utils/notify'
import { useDB, schema } from '../utils/db.js'
// 手机号正则
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 formData = {
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() ?? '',
source: 'customize-form',
}
// ── 写入数据库 ────────────────────────────────────────
const db = useDB()
const [record] = db.insert(schema.submissions)
.values({
type: 'customize',
data: JSON.stringify(formData),
ip,
})
.returning()
.all()
// ── 飞书通知(配置后生效)────────────────────────────
await notifyWecom('🌿 新定制需求 - 呼籁旅行', {
'编号': String(record.id),
'姓名': formData.name,
'手机': formData.phone,
'微信': formData.wechat || '(同手机号)',
'出行人数': `成人 ${formData.adults}${formData.children > 0 ? `、儿童 ${formData.children} 人(${formData.childAges.join('、')}岁)` : ''}`,
'出行日期': formData.date || '未填写',
'天数': formData.duration || '未选择',
'特殊需求': formData.activities.length ? formData.activities.join('、') : '无',
'预算': formData.budget || '未选择',
'补充说明': formData.remarks || '无',
})
// ── 返回 ──────────────────────────────────────────────
return {
success: true,
id: record.id,
message: '需求已收到,定制师将在2小时内联系您',
}
})