用户系统

This commit is contained in:
2026-04-15 00:21:43 +08:00
parent 97bbb3f306
commit 47c0863bab
23 changed files with 1640 additions and 56 deletions

View File

@@ -1,8 +1,10 @@
"use client";
import { useState, useRef, useEffect } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useApp } from "@/lib/app-context";
import { useAuth } from "@/lib/auth-context";
const NAV_ITEMS = [
{ href: "/", label: "对话" },
@@ -12,6 +14,20 @@ const NAV_ITEMS = [
export function TopNav() {
const pathname = usePathname();
const { sidebarCollapsed, setSidebarCollapsed } = useApp();
const { user, logout } = useAuth();
const [menuOpen, setMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!menuOpen) return;
function handleClick(e: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setMenuOpen(false);
}
}
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, [menuOpen]);
return (
<header className="flex-shrink-0 h-12 border-b border-[var(--border)] bg-[var(--bg-secondary)] flex items-center px-3 md:px-4 gap-3 md:gap-6">
@@ -64,21 +80,43 @@ export function TopNav() {
<div className="flex-1" />
{/* 全局搜索入口 — 移动端只显示图标 */}
<button
className="flex items-center gap-2 px-2 md:px-3 py-1.5 rounded-md text-sm
text-[var(--text-secondary)] border border-[var(--border)]
bg-[var(--bg-tertiary)] hover:border-[var(--accent)]
transition-colors cursor-pointer"
title="搜索"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="11" cy="11" r="8" />
<path d="M21 21l-4.35-4.35" />
</svg>
<span className="hidden sm:inline"></span>
<kbd className="hidden md:inline text-xs text-[var(--text-secondary)] opacity-50 ml-2">K</kbd>
</button>
{/* 用户菜单 */}
{user && (
<div className="relative" ref={menuRef}>
<button
onClick={() => setMenuOpen(!menuOpen)}
className="flex items-center gap-2 px-2 md:px-3 py-1.5 rounded-md text-sm
text-[var(--text-secondary)] hover:text-[var(--text-primary)]
hover:bg-[var(--bg-tertiary)] transition-colors cursor-pointer"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
<span className="hidden sm:inline max-w-[100px] truncate">
{user.display_name || user.username}
</span>
</button>
{menuOpen && (
<div className="absolute right-0 top-full mt-1 w-44 rounded-lg
bg-[var(--bg-secondary)] border border-[var(--border)]
shadow-xl py-1 z-50">
<div className="px-3 py-2 text-xs text-[var(--text-secondary)] border-b border-[var(--border)]">
{user.username}
{user.is_admin && <span className="ml-1 text-[var(--accent)]">()</span>}
</div>
<button
onClick={() => { setMenuOpen(false); logout(); }}
className="w-full text-left px-3 py-2 text-sm text-[var(--text-secondary)]
hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]
transition-colors cursor-pointer"
>
退
</button>
</div>
)}
</div>
)}
</header>
);
}