diff --git a/app/join/page.tsx b/app/join/page.tsx index 5e9cdb2..24133d8 100644 --- a/app/join/page.tsx +++ b/app/join/page.tsx @@ -141,6 +141,8 @@ export default function JoinPage() { const [fromVolunteer, setFromVolunteer] = useState(false); const [paying, setPaying] = useState(false); const initialWechatCheckDone = useRef(false); + const lastSubmitTime = useRef(0); + const SUBMIT_THROTTLE_MS = 2000; const fetchByWechat = async (w: string) => { if (!w.trim()) return; @@ -243,6 +245,26 @@ export default function JoinPage() { const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setError(null); + + const now = Date.now(); + if (now - lastSubmitTime.current < SUBMIT_THROTTLE_MS) { + return; + } + if (userRecord?.hasRecord) { + setError("该微信号已报名,请勿重复提交"); + return; + } + try { + const checkRes = await fetch(`/api/join/by-wechat?wechat_id=${encodeURIComponent(wechat.trim())}`, { cache: "no-store" }); + const checkData = await checkRes.json(); + if (checkData?.hasRecord) { + setError("该微信号已报名,请勿重复提交"); + fetchByWechat(wechat.trim()); + return; + } + } catch { + /* 校验失败时继续提交,由后端兜底 */ + } if (xiaohongshuUrl.trim() && urlError) { setError("请先修正小红书链接"); return; @@ -263,6 +285,7 @@ export default function JoinPage() { setError("请选择性别"); return; } + lastSubmitTime.current = now; setSubmitting(true); try { const formGenderFemale = gender === "女" || gender === "female"; diff --git a/next.config.ts b/next.config.ts index 095ed6f..c20d725 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,18 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { reactStrictMode: true, + async headers() { + return [ + { + source: "/:path*", + headers: [ + { key: "Cache-Control", value: "no-store, no-cache, must-revalidate, proxy-revalidate" }, + { key: "Pragma", value: "no-cache" }, + { key: "Expires", value: "0" }, + ], + }, + ]; + }, images: { remotePatterns: [ { protocol: "https", hostname: "picsum.photos", pathname: "/**" },