diff --git a/src/app/tv/me/page.tsx b/src/app/tv/me/page.tsx new file mode 100644 index 0000000..d45f5c1 --- /dev/null +++ b/src/app/tv/me/page.tsx @@ -0,0 +1,189 @@ +'use client'; + +import { + BadgeCheck, + Clock3, + Loader2, + LogOut, + ShieldCheck, + User, +} from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import { useEffect, useMemo, useState } from 'react'; + +import { clearAuthCookie, getAuthInfoFromBrowserCookie } from '@/lib/auth'; + +import TVLayout from '@/components/tv/TVLayout'; + +type AuthInfo = { + username?: string; + role?: 'owner' | 'admin' | 'user'; + timestamp?: number; + refreshExpires?: number; +}; + +function getRoleText(role?: AuthInfo['role']) { + switch (role) { + case 'owner': + return '站长'; + case 'admin': + return '管理员'; + case 'user': + return '用户'; + default: + return '访客'; + } +} + +function formatDateTime(value?: number) { + if (!value) return '当前会话'; + try { + return new Intl.DateTimeFormat('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }).format(new Date(value)); + } catch { + return '当前会话'; + } +} + +export default function TVMePage() { + const router = useRouter(); + const [ready, setReady] = useState(false); + const [authInfo, setAuthInfo] = useState(null); + const [loggingOut, setLoggingOut] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + const auth = getAuthInfoFromBrowserCookie(); + setAuthInfo(auth); + setReady(true); + }, []); + + useEffect(() => { + if (ready && !authInfo) { + router.replace('/tv/login?redirect=/tv/me'); + } + }, [authInfo, ready, router]); + + const username = authInfo?.username || 'default'; + const roleText = getRoleText(authInfo?.role || 'user'); + const avatarText = useMemo( + () => username.trim().charAt(0).toUpperCase() || 'D', + [username] + ); + + const handleLogout = async () => { + setLoggingOut(true); + setError(''); + try { + await fetch('/api/logout', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }); + } catch { + setError('登出请求失败,已清理本地登录状态。'); + } finally { + clearAuthCookie(); + router.replace('/tv/login'); + } + }; + + if (!ready || !authInfo) { + return ( + +
+ +

正在读取登录信息

+

请稍候,电视端会自动跳转。

+
+
+ ); + } + + return ( + +
+
+
+
+
+
+ + 已登录电视端 +
+
+
+ {avatarText} +
+
+

+ {username} +

+

+ 欢迎回来,继续享受大屏观影。 +

+
+
+ +
+
+
+ + 账号角色 +
+
{roleText}
+
+
+
+ + 登录时间 +
+
+ {formatDateTime(authInfo.timestamp)} +
+
+
+
+ + +
+
+
+
+ ); +} diff --git a/src/components/tv/TVLayout.tsx b/src/components/tv/TVLayout.tsx index 6698ac6..8a63e41 100644 --- a/src/components/tv/TVLayout.tsx +++ b/src/components/tv/TVLayout.tsx @@ -9,6 +9,7 @@ import { Search, Sparkles, Tv, + User, } from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; @@ -25,6 +26,7 @@ const navItems = [ { label: '综艺', href: '/tv/variety', icon: MonitorPlay }, { label: '直播', href: '/tv/live', icon: Radio }, { label: '私人影库', href: '/tv/private', icon: Heart }, + { label: '我的', href: '/tv/me', icon: User }, ]; export default function TVLayout({