From 7aa6c7f792d51f0a593636d4e90c1020291002b9 Mon Sep 17 00:00:00 2001 From: kurihada Date: Thu, 26 Feb 2026 14:42:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=A8=E5=B1=80=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=A4=B4=E5=83=8F=E5=BE=BD=E7=AB=A0=EF=BC=8C=E6=89=80=E6=9C=89?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8F=B3=E4=B8=8A=E8=A7=92=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 GlobalUserBadge 组件,固定在右上角,已登录显示头像+用户名,未登录显示登录按钮 - 通过 layout.tsx 全局挂载,仅在个人中心页隐藏 - userId.ts 登录/登出时派发 nowhatever_auth 事件,组件实时响应 - 移除各页面重复的用户指示器(首页、极速救场、周末契约大厅、个人中心顶栏退出按钮) - TopNav 右侧留出空间避免与全局徽章重叠 - 头像徽章采用暗色主题风格(bg-surface/80) --- src/app/blindbox/page.tsx | 16 ++++--- src/app/layout.tsx | 2 + src/app/page.tsx | 49 +------------------- src/app/panic/page.tsx | 40 ++-------------- src/app/profile/page.tsx | 7 --- src/components/GlobalUserBadge.tsx | 73 ++++++++++++++++++++++++++++++ src/components/MatchResult.tsx | 48 +++++++++++++++----- src/components/TopNav.tsx | 2 +- src/lib/userId.ts | 2 + 9 files changed, 129 insertions(+), 110 deletions(-) create mode 100644 src/components/GlobalUserBadge.tsx diff --git a/src/app/blindbox/page.tsx b/src/app/blindbox/page.tsx index 824335f..5f171ea 100644 --- a/src/app/blindbox/page.tsx +++ b/src/app/blindbox/page.tsx @@ -49,6 +49,16 @@ export default function BlindboxLobbyPage() { } }, []); + useEffect(() => { + const handler = () => { + const registered = isRegistered(); + setLoggedIn(registered); + setProfile(registered ? getCachedProfile() : null); + }; + window.addEventListener("nowhatever_auth", handler); + return () => window.removeEventListener("nowhatever_auth", handler); + }, []); + const fetchRooms = useCallback(async () => { const p = getCachedProfile(); if (!p) return; @@ -135,12 +145,6 @@ export default function BlindboxLobbyPage() { ADVENTURE ROULETTE

- {profile && ( -
- {profile.avatar} - {profile.username} -
- )} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 216ae40..e6ad5ac 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from "next"; import { Geist } from "next/font/google"; import "./globals.css"; +import GlobalUserBadge from "@/components/GlobalUserBadge"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -28,6 +29,7 @@ export default function RootLayout({ return ( + {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index b98f426..52f5c60 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,27 +1,12 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { motion } from "framer-motion"; -import { Zap, Gift, Clock, ChevronRight, User } from "lucide-react"; +import { Zap, Gift, Clock, ChevronRight } from "lucide-react"; import BrandLogo from "@/components/BrandLogo"; -import { getCachedProfile } from "@/lib/userId"; -import AuthModal from "@/components/AuthModal"; -import type { UserProfile } from "@/types"; export default function LandingPage() { const router = useRouter(); - const [profile, setProfile] = useState(null); - const [showAuth, setShowAuth] = useState(false); - - useEffect(() => { - setProfile(getCachedProfile()); - }, []); - - const handleAuth = useCallback((p: UserProfile) => { - setProfile(p); - setShowAuth(false); - }, []); return (
@@ -29,32 +14,6 @@ export default function LandingPage() {
- {/* User indicator */} - - {profile ? ( - - ) : ( - - )} - - {/* Header */} - setShowAuth(false)} - onAuth={handleAuth} - defaultTab="register" - />
); } diff --git a/src/app/panic/page.tsx b/src/app/panic/page.tsx index d1d508f..2e1cd3e 100644 --- a/src/app/panic/page.tsx +++ b/src/app/panic/page.tsx @@ -3,12 +3,10 @@ import { useState, useRef, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; -import { Plus, LogIn, Loader2, MapPin, Navigation, X, Users, Heart, Sparkles, ChevronRight, Flame, User, ArrowLeft } from "lucide-react"; -import { getUserId, getCachedProfile, getCachedPreferences } from "@/lib/userId"; -import { getAvatarBg } from "@/lib/avatars"; -import AuthModal from "@/components/AuthModal"; +import { Plus, LogIn, Loader2, MapPin, Navigation, X, Users, Heart, Sparkles, ChevronRight, Flame, ArrowLeft } from "lucide-react"; +import { getUserId, getCachedPreferences } from "@/lib/userId"; import { SCENES, getSceneConfig } from "@/lib/sceneConfig"; -import type { UserProfile, SceneType } from "@/types"; +import type { SceneType } from "@/types"; interface LocationSuggestion { id: string; @@ -90,13 +88,7 @@ export default function PanicPage() { const [scene, setScene] = useState("eat"); const sceneConfig = getSceneConfig(scene); - const [profile, setProfile] = useState(null); - const [authModalOpen, setAuthModalOpen] = useState(false); - useEffect(() => { - const cached = getCachedProfile(); - if (cached) setProfile(cached); - const prefs = getCachedPreferences(); if (prefs.cuisine) setCuisine(prefs.cuisine); if (prefs.priceRange) setPriceRange(prefs.priceRange); @@ -265,27 +257,6 @@ export default function PanicPage() { 返回 - {/* Profile / Auth button */} -
- {profile ? ( - - ) : ( - - )} -
- - setAuthModalOpen(false)} - onAuth={(p) => setProfile(p)} - />
); } diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index c4afcde..0debe0e 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -274,13 +274,6 @@ export default function ProfilePage() {

个人中心

-
diff --git a/src/components/GlobalUserBadge.tsx b/src/components/GlobalUserBadge.tsx new file mode 100644 index 0000000..b514e94 --- /dev/null +++ b/src/components/GlobalUserBadge.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { useRouter, usePathname } from "next/navigation"; +import { motion } from "framer-motion"; +import { User } from "lucide-react"; +import { getCachedProfile } from "@/lib/userId"; +import AuthModal from "@/components/AuthModal"; +import type { UserProfile } from "@/types"; + +const HIDDEN_PREFIXES = ["/profile"]; + +export default function GlobalUserBadge() { + const router = useRouter(); + const pathname = usePathname(); + const [profile, setProfile] = useState(null); + const [showAuth, setShowAuth] = useState(false); + const hidden = HIDDEN_PREFIXES.some((p) => pathname.startsWith(p)); + + useEffect(() => { + setProfile(getCachedProfile()); + }, [pathname]); + + useEffect(() => { + const handler = () => setProfile(getCachedProfile()); + window.addEventListener("nowhatever_auth", handler); + return () => window.removeEventListener("nowhatever_auth", handler); + }, []); + + const handleAuth = useCallback((p: UserProfile) => { + setProfile(p); + setShowAuth(false); + }, []); + + if (hidden) return null; + + return ( + <> + + {profile ? ( + + ) : ( + + )} + + + setShowAuth(false)} + onAuth={handleAuth} + /> + + ); +} diff --git a/src/components/MatchResult.tsx b/src/components/MatchResult.tsx index 54c882e..fb4c6c2 100644 --- a/src/components/MatchResult.tsx +++ b/src/components/MatchResult.tsx @@ -22,7 +22,13 @@ import { Heart, UserPlus, } from "lucide-react"; -import { Restaurant, MatchType, RunnerUp, SceneType, UserProfile } from "@/types"; +import { + Restaurant, + MatchType, + RunnerUp, + SceneType, + UserProfile, +} from "@/types"; import { fireCelebration, playChime } from "@/lib/celebrate"; import { isRegistered } from "@/lib/userId"; import ShareCardModal from "@/components/ShareCardModal"; @@ -237,11 +243,14 @@ export default function MatchResult({ setShowShareCard(true); }, []); - const handleAuth = useCallback((profile: UserProfile) => { - setRegistered(true); - setShowAuth(false); - showToast(`欢迎,${profile.username}!记录已保存`); - }, [showToast]); + const handleAuth = useCallback( + (profile: UserProfile) => { + setRegistered(true); + setShowAuth(false); + showToast(`欢迎,${profile.username}!记录已保存`); + }, + [showToast], + ); const handleFavorite = useCallback(async () => { if (!registered || favorited || favLoading) return; @@ -296,7 +305,12 @@ export default function MatchResult({ {isUnanimous ? ( @@ -311,7 +325,7 @@ export default function MatchResult({ animate={{ y: 0, opacity: 1 }} transition={{ delay: 0.35 }} > - {isSolo ? "帮你选好了!" : "就去这了!"} + {isSolo ? "帮你选好了" : "就去这了"} {registered && ( )} diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx index 29cd89f..63eed2e 100644 --- a/src/components/TopNav.tsx +++ b/src/components/TopNav.tsx @@ -47,7 +47,7 @@ export default function TopNav({ return ( <> -