交互原型大版本
This commit is contained in:
@@ -7,12 +7,14 @@ import { usePathname } from "next/navigation";
|
||||
import { useApp } from "@/lib/app-context";
|
||||
import { useAuth } from "@/lib/auth-context";
|
||||
import { ProfileModal } from "@/components/profile-modal";
|
||||
import { ProjectSwitcher } from "./project-switcher";
|
||||
import { slideUp } from "@/components/ui/motion-presets";
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{
|
||||
href: "/",
|
||||
label: "对话",
|
||||
label: "工作台",
|
||||
phase: "Phase 0",
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" />
|
||||
@@ -22,6 +24,7 @@ const NAV_ITEMS = [
|
||||
{
|
||||
href: "/gallery",
|
||||
label: "资源库",
|
||||
phase: "Phase 0",
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
@@ -30,6 +33,41 @@ const NAV_ITEMS = [
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: "/styles",
|
||||
label: "风格库",
|
||||
phase: "Phase 1",
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="13.5" cy="6.5" r=".5" />
|
||||
<circle cx="17.5" cy="10.5" r=".5" />
|
||||
<circle cx="8.5" cy="7.5" r=".5" />
|
||||
<circle cx="6.5" cy="12.5" r=".5" />
|
||||
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.55 0 1-.45 1-1v-3.5c0-.55.45-1 1-1h2c2.76 0 5-2.24 5-5 0-5.5-4.5-9.5-10-9.5z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: "/characters",
|
||||
label: "角色库",
|
||||
phase: "Phase 1",
|
||||
icon: (
|
||||
<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>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: "/training",
|
||||
label: "训练中心",
|
||||
phase: "Phase 2",
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 2L3 7v5c0 5 3.8 9.7 9 11 5.2-1.3 9-6 9-11V7l-9-5z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function TopNav() {
|
||||
@@ -53,7 +91,7 @@ export function TopNav() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="relative z-10 flex-shrink-0 h-14 border-b border-[var(--border)] surface-1 backdrop-blur-xl flex items-center px-4 md:px-5 gap-4 md:gap-6">
|
||||
<header className="relative z-10 flex-shrink-0 h-14 border-b border-[var(--border)] surface-1 backdrop-blur-xl flex items-center px-4 md:px-5 gap-3 md:gap-5">
|
||||
{/* 移动端汉堡菜单 */}
|
||||
<button
|
||||
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||||
@@ -79,27 +117,42 @@ export function TopNav() {
|
||||
<circle cx="23" cy="22.5" r="1" fill="white" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-base font-bold tracking-tight text-[var(--text-primary)]">
|
||||
<span className="text-base font-bold tracking-tight text-[var(--text-primary)] hidden sm:inline">
|
||||
EPEEKit
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* 项目切换器 */}
|
||||
<ProjectSwitcher />
|
||||
|
||||
{/* 导航 Tab */}
|
||||
<nav className="flex items-center gap-1.5">
|
||||
{NAV_ITEMS.map(({ href, label, icon }) => {
|
||||
<nav className="flex items-center gap-1 overflow-x-auto hide-scrollbar">
|
||||
{NAV_ITEMS.map(({ href, label, icon, phase }) => {
|
||||
const active = href === "/" ? pathname === "/" : pathname.startsWith(href);
|
||||
return (
|
||||
<Link
|
||||
key={href}
|
||||
href={href}
|
||||
className={`relative flex items-center gap-1.5 px-3 md:px-4 py-2 text-sm rounded-lg transition-all ${
|
||||
className={`relative flex items-center gap-1.5 px-2.5 md:px-3.5 py-2 text-sm rounded-lg transition-all whitespace-nowrap ${
|
||||
active
|
||||
? "glow-border text-[var(--accent)] font-medium !bg-[var(--bg-secondary)]"
|
||||
: "text-[var(--text-secondary)] hover:text-[var(--text-primary)] border border-transparent hover:border-[var(--border-glow)]"
|
||||
}`}
|
||||
title={`${label}(${phase})`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
<span className="hidden md:inline">{label}</span>
|
||||
{phase !== "Phase 0" && (
|
||||
<span
|
||||
className="hidden lg:inline text-[9px] px-1 py-px rounded font-semibold"
|
||||
style={{
|
||||
background: "rgba(184, 147, 90, 0.12)",
|
||||
color: "var(--gold)",
|
||||
}}
|
||||
>
|
||||
{phase.replace("Phase ", "P")}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -112,7 +165,7 @@ export function TopNav() {
|
||||
<div className="relative" ref={menuRef}>
|
||||
<button
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-sm
|
||||
text-[var(--text-secondary)] hover:text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] border border-transparent
|
||||
hover:border-[var(--border)] transition-all cursor-pointer"
|
||||
@@ -121,7 +174,7 @@ export function TopNav() {
|
||||
flex items-center justify-center text-[var(--accent)] text-xs font-semibold">
|
||||
{(user.display_name || user.username).charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<span className="hidden sm:inline max-w-[100px] truncate">
|
||||
<span className="hidden sm:inline max-w-[80px] truncate">
|
||||
{user.display_name || user.username}
|
||||
</span>
|
||||
</button>
|
||||
@@ -132,29 +185,72 @@ export function TopNav() {
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit={{ opacity: 0, y: 4, transition: { duration: 0.1 } }}
|
||||
className="absolute right-0 top-full mt-1.5 w-48 rounded-xl
|
||||
className="absolute right-0 top-full mt-1.5 w-56 rounded-xl
|
||||
surface-3 py-1.5 z-50"
|
||||
>
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); setProfileOpen(true); }}
|
||||
className="w-full text-left px-3.5 py-2 text-xs text-[var(--text-secondary)]
|
||||
hover:text-[var(--accent)] hover:bg-[var(--bg-tertiary)]
|
||||
border-b border-[var(--border)] transition-colors cursor-pointer
|
||||
flex items-center gap-1.5"
|
||||
>
|
||||
<div className="px-3.5 py-2 text-xs text-[var(--text-secondary)]
|
||||
border-b border-[var(--border)] flex items-center gap-1.5">
|
||||
<svg width="12" height="12" 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>{user.username}</span>
|
||||
{user.is_admin && <span className="text-[var(--accent)]">(管理员)</span>}
|
||||
<span className="truncate">{user.username}</span>
|
||||
{user.is_admin && <span className="text-[var(--accent)] font-medium">(管理员)</span>}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); setProfileOpen(true); }}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors
|
||||
flex items-center gap-2"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
||||
</svg>
|
||||
个人信息 / 记忆
|
||||
</button>
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-primary)]
|
||||
hover:bg-[var(--bg-tertiary)] cursor-pointer transition-colors
|
||||
flex items-center gap-2"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z" />
|
||||
</svg>
|
||||
项目设置
|
||||
<span className="ml-auto text-[9px] px-1 py-px rounded font-semibold"
|
||||
style={{ background: "rgba(184, 147, 90, 0.12)", color: "var(--gold)" }}>
|
||||
P1
|
||||
</span>
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); alert("全局设置(Phase 2 规划中)\n- API Key 配置\n- 界面偏好\n- 通知设置"); }}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-secondary)]
|
||||
hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]
|
||||
cursor-pointer transition-colors flex items-center gap-2"
|
||||
title="Phase 2 占位"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 20V10M6 20v-5M18 20V4" />
|
||||
</svg>
|
||||
全局设置
|
||||
<span className="ml-auto text-[9px] px-1 py-px rounded font-semibold"
|
||||
style={{ background: "rgba(184, 147, 90, 0.12)", color: "var(--gold)" }}>
|
||||
P2
|
||||
</span>
|
||||
</button>
|
||||
<div className="border-t border-[var(--border)] my-1" />
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); logout(); }}
|
||||
className="w-full text-left px-3.5 py-2 text-sm text-[var(--text-secondary)]
|
||||
hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]
|
||||
transition-colors cursor-pointer"
|
||||
hover:text-[var(--hot)] hover:bg-[var(--bg-tertiary)]
|
||||
transition-colors cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9" />
|
||||
</svg>
|
||||
退出登录
|
||||
</button>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user