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 (
<>
-