6bb0e65d4c
Task 4: 统一 amap.ts 为完整 API 客户端 - 扩展 amap.ts 为统一客户端(amapFetch 8s 超时 + 错误处理) - 导出 searchPlaceText/searchPlaceAround/getInputTips/reverseGeocode/getTransitDirection - 精简 4 个 location route 为单行调用,blindboxPlanGen 删除 ~80 行内联 API 代码 Task 2: 抽取 ShareCardShell 消除三兄弟重复 - 新建 ShareCardShell.tsx 共享外框/背景/品牌头/QR 底部 - RestaurantShareCard 406→268 行,BlindboxShareCard 341→173 行,BlindboxPlanShareCard 277→159 行 Task 3: 拆分 BlindboxPlan.tsx (742→371 行) - 提取 planUtils.ts (guessCategory + formatDuration) - 提取 PoiSearchField / SortablePlanItem / PlanItemEditModal 三个独立组件 Task 1: 拆分 blindbox/[code]/page.tsx 上帝组件 (1300→509 行) - 提取 useBlindboxRoom / useBlindboxIdeas / useBlindboxPlan / useBlindboxDraw 四个 hooks - 提取 BlindboxPoolPhase / BlindboxRevealPhase 两个子组件 - 主页面仅保留 phase 协调 + hook 组装 + 子组件渲染 Task 5: 统一 SWR 数据获取层 - 新建 fetcher.ts (FetchError 携带 status,401 不重试) - 新建 useBlindboxRooms / useAchievements / useFavorites SWR hooks - useRoomPolling 改用共享 fetcher - blindbox 大厅/成就/个人中心页面删除手写 fetch 样板代码 - JWT 过期时自动弹出登录框而非反复重试
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useRef, useCallback } from "react";
|
|
import { useAnimation } from "framer-motion";
|
|
import confetti from "canvas-confetti";
|
|
import type { DrawnIdea } from "@/components/BlindboxDrawnHistory";
|
|
import type { RoomInfo } from "@/hooks/useBlindboxRoom";
|
|
import type { UserProfile } from "@/types";
|
|
|
|
type Phase = "pool" | "shaking" | "reveal" | "time_select" | "planning" | "plan_reveal";
|
|
|
|
export function useBlindboxDraw(
|
|
room: RoomInfo | null,
|
|
profile: UserProfile | null,
|
|
poolCount: number,
|
|
setPoolCount: React.Dispatch<React.SetStateAction<number>>,
|
|
setDrawnHistory: React.Dispatch<React.SetStateAction<DrawnIdea[]>>,
|
|
setError: (e: string) => void,
|
|
setPhase: (p: Phase) => void,
|
|
) {
|
|
const [revealedIdea, setRevealedIdea] = useState<DrawnIdea | null>(null);
|
|
const [showShareCard, setShowShareCard] = useState(false);
|
|
const boxControls = useAnimation();
|
|
const confettiAliveRef = useRef(false);
|
|
const timersRef = useRef<ReturnType<typeof setTimeout>[]>([]);
|
|
|
|
const fireConfetti = useCallback(() => {
|
|
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 || !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);
|
|
};
|
|
timersRef.current.push(setTimeout(frame, 200));
|
|
}, []);
|
|
|
|
const handleDraw = async () => {
|
|
if (poolCount === 0 || !profile || !room) {
|
|
setError("盒子是空的,先往里面塞点想法吧!");
|
|
return;
|
|
}
|
|
|
|
setPhase("shaking");
|
|
setError("");
|
|
|
|
await boxControls.start({
|
|
rotate: [0, -8, 8, -10, 10, -12, 12, -8, 8, -4, 4, 0],
|
|
scale: [1, 1.05, 0.95, 1.08, 0.92, 1.1, 0.9, 1.05, 0.95, 1],
|
|
transition: { duration: 2.5, ease: "easeInOut" },
|
|
});
|
|
|
|
try {
|
|
const res = await fetch("/api/blindbox/draw", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ roomId: room.id, userId: profile.id }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const data = await res.json();
|
|
throw new Error(data.error || "抽取失败");
|
|
}
|
|
|
|
const idea = await res.json();
|
|
setRevealedIdea(idea);
|
|
setPhase("reveal");
|
|
setPoolCount((c) => Math.max(0, c - 1));
|
|
setDrawnHistory((prev) => [idea, ...prev]);
|
|
fireConfetti();
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : "抽取失败");
|
|
setPhase("pool");
|
|
}
|
|
};
|
|
|
|
const handleContinue = useCallback(() => {
|
|
setPhase("pool");
|
|
setRevealedIdea(null);
|
|
setShowShareCard(false);
|
|
}, [setPhase]);
|
|
|
|
return {
|
|
revealedIdea,
|
|
showShareCard,
|
|
setShowShareCard,
|
|
boxControls,
|
|
fireConfetti,
|
|
handleDraw,
|
|
handleContinue,
|
|
};
|
|
}
|