From 1e39c72a63ac2aa3def3887c4b25bd67f12c30ac Mon Sep 17 00:00:00 2001 From: kurihada Date: Fri, 27 Feb 2026 18:38:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(blindbox):=20=E4=BF=AE=E5=A4=8D=E6=88=BF?= =?UTF-8?q?=E9=97=B4=E5=88=9B=E5=BB=BA=E5=90=8E=E8=BF=94=E5=9B=9E=E5=A4=A7?= =?UTF-8?q?=E5=8E=85=E4=B8=8D=E5=8F=AF=E8=A7=81=20+=20=E5=A4=A7=E5=8E=85?= =?UTF-8?q?=E6=88=BF=E9=97=B4=E5=88=97=E8=A1=A8=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复房主退出房间时误删整间房的问题,改为返回大厅(房间保留) - 修复大厅页 fetchRooms 时序依赖导致导航回来不刷新的问题 - fetch 添加 cache:no-store + router.refresh() 确保数据始终最新 - 房间列表增加 max-h 滚动 + 底部渐变遮罩防溢出 - 大厅房间卡片支持内联删除/退出(··· 按钮 → 确认栏) - rooms API 返回 creatorId 以区分房主/成员操作 --- src/app/api/blindbox/rooms/route.ts | 1 + src/app/blindbox/[code]/page.tsx | 77 ++++--- src/app/blindbox/page.tsx | 300 ++++++++++++++++++++-------- 3 files changed, 270 insertions(+), 108 deletions(-) diff --git a/src/app/api/blindbox/rooms/route.ts b/src/app/api/blindbox/rooms/route.ts index 84400c9..824a10a 100644 --- a/src/app/api/blindbox/rooms/route.ts +++ b/src/app/api/blindbox/rooms/route.ts @@ -34,6 +34,7 @@ export const GET = apiHandler(async (req) => { id: m.room.id, code: m.room.code, name: m.room.name, + creatorId: m.room.creatorId, memberCount: m.room._count.members, ideaCount: m.room._count.ideas, poolCount: 0, diff --git a/src/app/blindbox/[code]/page.tsx b/src/app/blindbox/[code]/page.tsx index 40c6b08..4650768 100644 --- a/src/app/blindbox/[code]/page.tsx +++ b/src/app/blindbox/[code]/page.tsx @@ -590,6 +590,12 @@ export default function BlindboxRoomPage() { const isCreator = profile?.id === room?.creatorId; + const handleBackToLobby = useCallback(() => { + router.push("/blindbox"); + router.refresh(); + }, [router]); + + /** Non-creator: leave room (remove membership). Creator: delete room (after confirm). */ const handleLeaveOrDelete = async () => { if (!confirmLeave) { setConfirmLeave(true); @@ -608,7 +614,8 @@ export default function BlindboxRoomPage() { const data = await res.json(); throw new Error(data.error || "操作失败"); } - router.replace("/blindbox"); + router.push("/blindbox"); + router.refresh(); } catch (e) { toast.show(e instanceof Error ? e.message : "操作失败"); setConfirmLeave(false); @@ -1130,34 +1137,58 @@ export default function BlindboxRoomPage() { /> )} - {/* Leave / Delete — hidden during plan view */} + {/* Leave / Back — hidden during plan view. Creator: 返回大厅 (no delete) + optional 删除房间. Non-creator: 退出房间. */} {isMember && room && phase !== "plan_reveal" && phase !== "planning" && ( - + {isCreator ? ( + <> + + + + ) : ( + + )} )} diff --git a/src/app/blindbox/page.tsx b/src/app/blindbox/page.tsx index b0971f7..7478b8c 100644 --- a/src/app/blindbox/page.tsx +++ b/src/app/blindbox/page.tsx @@ -11,24 +11,158 @@ import { Users, Sparkles, ChevronRight, + MoreHorizontal, + Trash2, + LogOut, + Loader2, + X, } from "lucide-react"; import { getCachedProfile, isRegistered } from "@/lib/userId"; import AuthModal from "@/components/AuthModal"; import Button from "@/components/Button"; import Input from "@/components/Input"; import { BlindboxListSkeleton } from "@/components/Skeleton"; +import { useToast } from "@/hooks/useToast"; import type { UserProfile } from "@/types"; interface RoomSummary { id: string; code: string; name: string; + creatorId: string; memberCount: number; poolCount: number; members: { id: string; username: string; avatar: string }[]; lastDrawn: { content: string; createdAt: string } | null; } +/* ── Room card with inline delete ────────────────────────── */ + +function RoomCard({ + room, + index, + isCreator, + confirmDelete, + deleting, + onNavigate, + onRequestDelete, + onConfirmDelete, + onCancelDelete, +}: { + room: RoomSummary; + index: number; + isCreator: boolean; + confirmDelete: boolean; + deleting: boolean; + onNavigate: () => void; + onRequestDelete: () => void; + onConfirmDelete: () => void; + onCancelDelete: () => void; +}) { + return ( + + {/* Main card row */} + + + {/* Inline confirm bar */} + + {confirmDelete && ( + +
+ + {isCreator ? "删除房间?所有想法将被清除" : "确认退出该房间?"} + + +
+
+ )} +
+
+ ); +} + export default function BlindboxLobbyPage() { const router = useRouter(); const [hydrated, setHydrated] = useState(false); @@ -38,6 +172,7 @@ export default function BlindboxLobbyPage() { const [rooms, setRooms] = useState([]); const [loading, setLoading] = useState(true); + const toast = useToast(); const createNameRef = useRef(null); const joinCodeRef = useRef(null); const [joinCodeLength, setJoinCodeLength] = useState(0); @@ -46,24 +181,8 @@ export default function BlindboxLobbyPage() { const [error, setError] = useState(""); const [loadError, setLoadError] = useState(false); - useEffect(() => { - const registered = isRegistered(); - setLoggedIn(registered); - if (registered) { - setProfile(getCachedProfile()); - } - setHydrated(true); - }, []); - - useEffect(() => { - const handler = () => { - const registered = isRegistered(); - setLoggedIn(registered); - setProfile(registered ? getCachedProfile() : null); - }; - window.addEventListener("nowhatever_auth", handler); - return () => window.removeEventListener("nowhatever_auth", handler); - }, []); + const [confirmDeleteId, setConfirmDeleteId] = useState(null); + const [deletingId, setDeletingId] = useState(null); const fetchRooms = useCallback(async (silent = false) => { const p = getCachedProfile(); @@ -73,7 +192,7 @@ export default function BlindboxLobbyPage() { setLoadError(false); } try { - const res = await fetch(`/api/blindbox/rooms?userId=${p.id}`); + const res = await fetch(`/api/blindbox/rooms?userId=${p.id}`, { cache: "no-store" }); if (!res.ok) throw new Error(); const data = await res.json(); setRooms(Array.isArray(data.rooms) ? data.rooms : []); @@ -86,26 +205,49 @@ export default function BlindboxLobbyPage() { }, []); useEffect(() => { - if (loggedIn) fetchRooms(); - else setLoading(false); - }, [loggedIn, fetchRooms]); + const registered = isRegistered(); + setLoggedIn(registered); + if (registered) { + setProfile(getCachedProfile()); + fetchRooms(); + } else { + setLoading(false); + } + setHydrated(true); + }, [fetchRooms]); + + useEffect(() => { + const handler = () => { + const registered = isRegistered(); + setLoggedIn(registered); + setProfile(registered ? getCachedProfile() : null); + if (registered) fetchRooms(); + }; + window.addEventListener("nowhatever_auth", handler); + return () => window.removeEventListener("nowhatever_auth", handler); + }, [fetchRooms]); useEffect(() => { if (!loggedIn) return; - const onFocus = () => fetchRooms(true); - window.addEventListener("focus", onFocus); - return () => window.removeEventListener("focus", onFocus); + const refresh = () => fetchRooms(true); + window.addEventListener("focus", refresh); + window.addEventListener("pageshow", refresh); + return () => { + window.removeEventListener("focus", refresh); + window.removeEventListener("pageshow", refresh); + }; }, [loggedIn, fetchRooms]); useEffect(() => { if (rooms.length > 0) setJoinCodeLength(0); }, [rooms.length]); - const handleAuth = (p: UserProfile) => { + const handleAuth = useCallback((p: UserProfile) => { setProfile(p); setLoggedIn(true); setShowAuth(false); - }; + fetchRooms(); + }, [fetchRooms]); const handleCreate = async () => { if (creating || !profile) return; @@ -149,6 +291,29 @@ export default function BlindboxLobbyPage() { } }; + const handleDeleteRoom = useCallback(async (room: RoomSummary) => { + if (deletingId || !profile) return; + setDeletingId(room.id); + try { + const res = await fetch(`/api/blindbox/room/${room.code}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId: profile.id }), + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "操作失败"); + } + setRooms((prev) => prev.filter((r) => r.id !== room.id)); + setConfirmDeleteId(null); + toast.show(room.creatorId === profile.id ? "房间已删除" : "已退出房间"); + } catch (e) { + toast.show(e instanceof Error ? e.message : "操作失败"); + } finally { + setDeletingId(null); + } + }, [deletingId, profile, toast]); + return (
{/* Ambient */} @@ -270,7 +435,7 @@ export default function BlindboxLobbyPage() {

加载房间失败

{/* Room list */} -
- {rooms.map((room, i) => ( - router.push(`/blindbox/${room.code}`)} - className="group flex w-full items-center gap-3 rounded-2xl bg-surface p-4 text-left ring-1 ring-border transition-all hover:bg-elevated hover:ring-purple-500/30" - initial={{ opacity: 0, x: -20 }} - animate={{ opacity: 1, x: 0 }} - transition={{ delay: i * 0.06 }} - whileTap={{ scale: 0.98 }} - > - {/* Icon */} -
- -
- - {/* Info */} -
-

{room.name}

-
- - - {room.memberCount} - - - - {room.poolCount} 待抽 - -
- {room.lastDrawn && ( -

- 最近抽中:{room.lastDrawn.content} -

- )} -
- - {/* Members preview */} -
- {room.members.slice(0, 3).map((m) => ( -
- {m.avatar} -
- ))} - {room.memberCount > 3 && ( -
- +{room.memberCount - 3} -
- )} -
- - -
- ))} +
+
+ + {rooms.map((room, i) => ( + router.push(`/blindbox/${room.code}`)} + onRequestDelete={() => setConfirmDeleteId(room.id)} + onConfirmDelete={() => handleDeleteRoom(room)} + onCancelDelete={() => setConfirmDeleteId(null)} + /> + ))} + +
+ {rooms.length > 3 && ( +
+ )}
)}