feat(admin): 独立后台壳、站点分析与聊天监管,公开站拆出主题切换与访问埋点
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,8 +6,6 @@ import { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
Menu,
|
||||
X,
|
||||
Sun,
|
||||
Moon,
|
||||
LogIn,
|
||||
UserPlus,
|
||||
LogOut,
|
||||
@@ -29,6 +27,7 @@ import { realtime, RT_CHAT_UNREAD } from "@/lib/realtime";
|
||||
import type { RtChatUnreadData } from "@/lib/realtime";
|
||||
import { onDocumentVisible } from "@/lib/visibilityReconcile";
|
||||
import NotificationBell from "./NotificationBell";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
import Avatar from "./Avatar";
|
||||
|
||||
export default function Header({
|
||||
@@ -49,7 +48,6 @@ export default function Header({
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [theme, setTheme] = useState<"light" | "dark">(initialTheme);
|
||||
// 登录态以 SSR 直出为初始值,F5 无切换闪动;router.refresh() 后随 props 校正
|
||||
const [user, setUser] = useState<User | null>(initialUser);
|
||||
const [unread, setUnread] = useState(initialUnread);
|
||||
@@ -148,11 +146,6 @@ export default function Header({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// cookie 为单一真相源,客户端导航后校正(预取载荷可能回滚类名)
|
||||
const isDark = document.cookie.match(/(?:^|;\s*)j13-theme=dark/);
|
||||
const savedTheme: "light" | "dark" = isDark ? "dark" : "light";
|
||||
document.documentElement.classList.toggle("dark", savedTheme === "dark");
|
||||
setTheme(savedTheme);
|
||||
setSearchQuery(new URLSearchParams(window.location.search).get("q")?.trim() || "");
|
||||
}, [pathname]);
|
||||
|
||||
@@ -180,13 +173,6 @@ export default function Header({
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme === "dark" ? "light" : "dark";
|
||||
setTheme(newTheme);
|
||||
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||
document.cookie = `j13-theme=${newTheme}; path=/; max-age=31536000`;
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await apiLogout();
|
||||
@@ -280,15 +266,11 @@ export default function Header({
|
||||
</kbd>
|
||||
</div>
|
||||
</form>
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
<ThemeToggle
|
||||
initialTheme={initialTheme}
|
||||
className={`${iconBtn} hidden lg:inline-flex`}
|
||||
style={iconBtnStyle}
|
||||
aria-label="切换主题"
|
||||
data-tip={theme === "dark" ? "切换浅色" : "切换深色"}
|
||||
>
|
||||
{theme === "dark" ? <Sun size={17} /> : <Moon size={17} />}
|
||||
</button>
|
||||
/>
|
||||
{user && (
|
||||
<Link
|
||||
href="/chat"
|
||||
@@ -407,10 +389,12 @@ export default function Header({
|
||||
{!user && allowRegister && <MobileLink href="/register">注册</MobileLink>}
|
||||
</div>
|
||||
<div className="pt-3 mt-2 border-t flex items-center justify-between" style={{ borderColor: "var(--line)" }}>
|
||||
<button onClick={toggleTheme} className="btn btn-line btn-sm">
|
||||
{theme === "dark" ? <Sun size={14} /> : <Moon size={14} />}
|
||||
{theme === "dark" ? "浅色模式" : "深色模式"}
|
||||
</button>
|
||||
<ThemeToggle
|
||||
initialTheme={initialTheme}
|
||||
labelMode="labeled"
|
||||
iconSize={14}
|
||||
className="btn btn-line btn-sm"
|
||||
/>
|
||||
{user && (
|
||||
<button onClick={handleLogout} className="btn btn-danger btn-sm">
|
||||
<LogOut size={14} /> 退出
|
||||
|
||||
Reference in New Issue
Block a user