feat(admin): 独立后台壳、站点分析与聊天监管,公开站拆出主题切换与访问埋点

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 04:00:14 +08:00
parent cda5fad2e5
commit 9a55c2f6f2
41 changed files with 5655 additions and 1810 deletions

View File

@@ -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} /> 退出