From 5adfe8d3f1f5c40d522b4b110a6d334d5022c192 Mon Sep 17 00:00:00 2001 From: kurihada Date: Thu, 26 Feb 2026 20:21:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E6=B8=85=E7=90=86=20=E2=80=94=20ShareCard=20=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=20+=20=E5=AE=9A=E6=97=B6=E5=99=A8=20+=20=E6=AD=BB?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #20: ShareCardModal useEffect 依赖改为 imageSrc 字符串,避免对象引用变化重复加载 - #21: BlindboxRoomPage 所有 setTimeout/rAF 统一收集,unmount 时清理 - #25: 删除未使用的 confettiCanvasRef 和 canvas 元素 --- src/app/blindbox/[code]/page.tsx | 23 ++++++++++++++++------- src/components/ShareCardModal.tsx | 11 +++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/app/blindbox/[code]/page.tsx b/src/app/blindbox/[code]/page.tsx index 6ca60b7..5f377de 100644 --- a/src/app/blindbox/[code]/page.tsx +++ b/src/app/blindbox/[code]/page.tsx @@ -65,7 +65,15 @@ export default function BlindboxRoomPage() { const boxControls = useAnimation(); const inputRef = useRef(null); - const confettiCanvasRef = useRef(null); + const timersRef = useRef[]>([]); + const confettiAliveRef = useRef(false); + + useEffect(() => { + return () => { + timersRef.current.forEach(clearTimeout); + confettiAliveRef.current = false; + }; + }, []); useEffect(() => { if (!isRegistered()) { @@ -121,7 +129,8 @@ export default function BlindboxRoomPage() { useEffect(() => { if (isMember && inputRef.current) { - setTimeout(() => inputRef.current?.focus(), 300); + const t = setTimeout(() => inputRef.current?.focus(), 300); + timersRef.current.push(t); } }, [isMember]); @@ -162,7 +171,7 @@ export default function BlindboxRoomPage() { setPoolCount((c) => c + 1); setMyIdeas((prev) => [{ id, content: text, createdAt: new Date().toISOString() }, ...prev]); setSubmitFlash(true); - setTimeout(() => setSubmitFlash(false), 600); + timersRef.current.push(setTimeout(() => setSubmitFlash(false), 600)); boxControls.start({ scale: [1, 1.08, 1], rotate: [0, -3, 3, 0], @@ -256,14 +265,15 @@ export default function BlindboxRoomPage() { const fireConfetti = () => { const colors = ["#a855f7", "#6366f1", "#ec4899", "#f59e0b", "#10b981"]; confetti({ particleCount: 100, spread: 120, origin: { y: 0.4 }, colors, startVelocity: 45, ticks: 250 }); + confettiAliveRef.current = true; const end = Date.now() + 3000; const frame = () => { - if (Date.now() > end) return; + if (Date.now() > end || !confettiAliveRef.current) return; confetti({ particleCount: 3, angle: 60, spread: 55, origin: { x: 0, y: 0.6 }, colors, startVelocity: 35, ticks: 150 }); confetti({ particleCount: 3, angle: 120, spread: 55, origin: { x: 1, y: 0.6 }, colors, startVelocity: 35, ticks: 150 }); requestAnimationFrame(frame); }; - setTimeout(frame, 200); + timersRef.current.push(setTimeout(frame, 200)); }; const { share, copyToClipboard } = useShare(); @@ -287,7 +297,7 @@ export default function BlindboxRoomPage() { const handleLeaveOrDelete = async () => { if (!confirmLeave) { setConfirmLeave(true); - setTimeout(() => setConfirmLeave(false), 3000); + timersRef.current.push(setTimeout(() => setConfirmLeave(false), 3000)); return; } if (leaving || !profile || !room) return; @@ -319,7 +329,6 @@ export default function BlindboxRoomPage() { return (
- {/* Header */}
diff --git a/src/components/ShareCardModal.tsx b/src/components/ShareCardModal.tsx index 1b379a8..93ad04b 100644 --- a/src/components/ShareCardModal.tsx +++ b/src/components/ShareCardModal.tsx @@ -38,21 +38,20 @@ export default function ShareCardModal({ const [imageDataUrl, setImageDataUrl] = useState(null); const [imageLoading, setImageLoading] = useState(false); + const imageSrc = data.type === "restaurant" ? data.restaurant.images?.[0] : undefined; + useEffect(() => { if (!open) { setImageDataUrl(null); return; } - if (data.type !== "restaurant") return; - - const src = data.restaurant.images?.[0]; - if (!src) return; + if (!imageSrc) return; setImageLoading(true); - loadImageAsDataUrl(src) + loadImageAsDataUrl(imageSrc) .then(setImageDataUrl) .finally(() => setImageLoading(false)); - }, [open, data]); + }, [open, imageSrc]); const handleGenerate = useCallback(async (): Promise => { if (!cardRef.current) return null;