Skip to content

Commit c19d0b8

Browse files
committed
feat(header): 添加用户登录状态管理功能
在Header组件中新增客户端脚本,用于根据用户登录状态动态显示登录按钮或用户头像。同时,在ScrollHeader组件中添加了用户登录状态和用户数据的存储与更新逻辑,以支持登录状态的持久化和界面更新
1 parent 8db0740 commit c19d0b8

2 files changed

Lines changed: 136 additions & 24 deletions

File tree

blog-web/src/components/Header.astro

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,58 @@ const getTabClass = (isActive: boolean) => {
1515
};
1616
---
1717

18+
<script>
19+
// 客户端脚本,用于处理用户登录状态
20+
document.addEventListener("DOMContentLoaded", () => {
21+
const loginBtn = document.getElementById("login-btn");
22+
const userAvatar = document.getElementById("user-avatar");
23+
const logoutBtn = document.getElementById("logout-btn");
24+
25+
// 检查用户是否已登录
26+
const token = localStorage.getItem("token");
27+
const userData = localStorage.getItem("user")
28+
? JSON.parse(localStorage.getItem("user") || "")
29+
: null;
30+
31+
if (token && userData) {
32+
// 用户已登录,显示头像,隐藏登录按钮
33+
if (loginBtn) loginBtn.style.display = "none";
34+
if (userAvatar) {
35+
userAvatar.style.display = "flex";
36+
37+
// 设置头像图片
38+
const avatarImg = userAvatar.querySelector(".avatar img");
39+
if (avatarImg && userData.avatar_url) {
40+
avatarImg.setAttribute("src", userData.avatar_url);
41+
avatarImg.setAttribute(
42+
"alt",
43+
userData.username || "用户头像",
44+
);
45+
}
46+
47+
// 设置用户名
48+
const usernameEl = document.getElementById("username");
49+
if (usernameEl && userData.username) {
50+
usernameEl.textContent = userData.username;
51+
}
52+
}
53+
54+
// 添加登出功能
55+
if (logoutBtn) {
56+
logoutBtn.addEventListener("click", () => {
57+
localStorage.removeItem("token");
58+
localStorage.removeItem("user");
59+
window.location.href = "/";
60+
});
61+
}
62+
} else {
63+
// 用户未登录,显示登录按钮,隐藏头像
64+
if (loginBtn) loginBtn.style.display = "flex";
65+
if (userAvatar) userAvatar.style.display = "none";
66+
}
67+
});
68+
</script>
69+
1870
<ScrollHeader client:load>
1971
<div class="navbar container mx-auto px-4 h-16">
2072
<div class="navbar-start">
@@ -91,9 +143,44 @@ const getTabClass = (isActive: boolean) => {
91143
</div>
92144
<div class="navbar-end">
93145
<ThemeToggle client:load />
94-
<button class="btn btn-primary ml-2">
146+
147+
<!-- 登录按钮 (默认显示) -->
148+
<button id="login-btn" class="btn btn-primary ml-2">
95149
<a href="/auth">登录</a>
96150
</button>
151+
152+
<!-- 用户头像和下拉菜单 (默认隐藏) -->
153+
<div
154+
id="user-avatar"
155+
class="dropdown dropdown-end ml-2"
156+
style="display: none;"
157+
>
158+
<div
159+
tabindex="0"
160+
role="button"
161+
class="btn btn-ghost btn-circle avatar"
162+
>
163+
<div class="w-10 rounded-full">
164+
<img
165+
alt="用户头像"
166+
src="https://daisyui.com/images/stock/photo-1534528741775-53994a69daeb.jpg"
167+
/>
168+
</div>
169+
</div>
170+
<ul
171+
tabindex="0"
172+
class="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-100 rounded-box w-52"
173+
>
174+
<li>
175+
<a class="justify-between">
176+
<span id="username">用户名</span>
177+
<span class="badge">个人资料</span>
178+
</a>
179+
</li>
180+
<li><a href="/dashboard">控制面板</a></li>
181+
<li><a id="logout-btn">退出登录</a></li>
182+
</ul>
183+
</div>
97184
</div>
98185
</div>
99186
</ScrollHeader>

blog-web/src/components/solid/ScrollHeader.tsx

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import { createSignal, createEffect, onCleanup, onMount, createMemo } from "solid-js";
2+
import type { JSX } from "solid-js";
23

3-
export default function ScrollHeader(props) {
4+
interface ScrollHeaderProps {
5+
children: JSX.Element;
6+
}
7+
8+
export default function ScrollHeader(props: ScrollHeaderProps) {
49
const [isVisible, setIsVisible] = createSignal(true);
510
const [lastScrollY, setLastScrollY] = createSignal(0);
611
const [isAtTop, setIsAtTop] = createSignal(true);
712
const [theme, setTheme] = createSignal('light');
8-
13+
const [isLoggedIn, setIsLoggedIn] = createSignal(false);
14+
15+
interface UserData {
16+
username?: string;
17+
avatar_url?: string;
18+
[key: string]: any;
19+
}
20+
21+
const [userData, setUserData] = createSignal<UserData | null>(null);
22+
923
// Handle scroll events with debounce
1024
const handleScroll = () => {
1125
const currentScrollY = window.scrollY;
12-
26+
1327
// Check if at top of page
1428
if (currentScrollY < 10) {
1529
setIsAtTop(true);
@@ -19,10 +33,10 @@ export default function ScrollHeader(props) {
1933
if (isAtTop()) {
2034
setIsAtTop(false);
2135
}
22-
36+
2337
// Check scroll direction
2438
const isScrollingUp = currentScrollY < lastScrollY();
25-
39+
2640
if (isScrollingUp) {
2741
// Scrolling up - show header
2842
setIsVisible(true);
@@ -33,14 +47,14 @@ export default function ScrollHeader(props) {
3347
}
3448
}
3549
}
36-
50+
3751
// Update last scroll position
3852
setLastScrollY(currentScrollY);
3953
};
40-
54+
4155
// Throttle scroll events using requestAnimationFrame
4256
let isThrottled = false;
43-
57+
4458
const onScroll = () => {
4559
if (!isThrottled) {
4660
requestAnimationFrame(() => {
@@ -50,9 +64,9 @@ export default function ScrollHeader(props) {
5064
isThrottled = true;
5165
}
5266
};
53-
67+
5468
// Listen for theme changes in localStorage
55-
const handleStorageChange = (e) => {
69+
const handleStorageChange = (e: StorageEvent) => {
5670
if (typeof localStorage !== 'undefined' && e.key === 'theme') {
5771
setTheme(e.newValue || 'light');
5872
}
@@ -63,41 +77,52 @@ export default function ScrollHeader(props) {
6377
setLastScrollY(window.scrollY);
6478
// Initialize header state
6579
handleScroll();
66-
80+
6781
// Set initial theme from localStorage (only available in browser)
6882
if (typeof localStorage !== 'undefined') {
6983
setTheme(localStorage.getItem('theme') || 'light');
84+
85+
// 检查用户登录状态
86+
const token = localStorage.getItem('token');
87+
const user = localStorage.getItem('user');
88+
89+
if (token && user) {
90+
setIsLoggedIn(true);
91+
try {
92+
setUserData(JSON.parse(user));
93+
} catch (e) {
94+
console.error('解析用户数据失败', e);
95+
}
96+
}
7097
}
71-
98+
7299
// Listen for theme changes from other components
73100
window.addEventListener('storage', handleStorageChange);
74101
});
75-
102+
76103
createEffect(() => {
77104
// Add scroll event listener
78105
window.addEventListener("scroll", onScroll, { passive: true });
79-
106+
80107
onCleanup(() => {
81108
// Remove scroll event listener
82109
window.removeEventListener("scroll", onScroll);
83110
window.removeEventListener('storage', handleStorageChange);
84111
});
85112
});
86-
113+
87114
// Theme-based background color class
88115
const bgColorClass = createMemo(() => {
89-
return theme() === 'dark'
90-
? 'bg-base-100/80 backdrop-blur-sm shadow-md'
116+
return theme() === 'dark'
117+
? 'bg-base-100/80 backdrop-blur-sm shadow-md'
91118
: 'bg-base-100/95 backdrop-blur-sm shadow-md';
92119
});
93-
120+
94121
return (
95122
<header
96-
class={`fixed top-0 w-full z-50 transition-all duration-300 transform ${
97-
isVisible() ? "translate-y-0" : "-translate-y-full"
98-
} ${
99-
!isAtTop() ? bgColorClass() : "bg-base-100"
100-
}`}
123+
class={`fixed top-0 w-full z-50 transition-all duration-300 transform ${isVisible() ? "translate-y-0" : "-translate-y-full"
124+
} ${!isAtTop() ? bgColorClass() : "bg-base-100"
125+
}`}
101126
>
102127
{props.children}
103128
</header>

0 commit comments

Comments
 (0)