侧边栏底部改为登录用户名 + 暗夜模式开关

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-06 12:57:31 +08:00
parent fc69f51e23
commit d2f428ec86
+69 -16
View File
@@ -1,5 +1,6 @@
import { NavLink, Outlet } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import { useState } from 'react'
import { NavLink, Outlet, useNavigate } from 'react-router-dom'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { motion } from 'framer-motion'
import { ToastContainer } from '@/components/Toast'
import { AlertToastContainer } from '@/components/AlertToast'
@@ -10,7 +11,6 @@ import { StockAnalysisBubble } from '@/components/stock-analysis/StockAnalysisBu
import {
useSettings,
usePreferences,
useVersion,
} from '@/lib/useSharedQueries'
import { QK } from '@/lib/queryKeys'
import {
@@ -25,6 +25,10 @@ import {
Layers3,
Landmark,
BookOpenCheck,
Moon,
Sun,
User,
LogOut,
} from 'lucide-react'
import { Logo } from './Logo'
import { api } from '@/lib/api'
@@ -76,14 +80,42 @@ function AIConfigBadge({ configured, model }: { configured?: boolean; model?: st
export function Layout() {
// ===== 共享 hooks (替代内联 useQuery) =====
const { data: settingsState } = useSettings()
const { data: versionData } = useVersion()
const { data: prefs } = usePreferences()
const { data: analysisMenus } = useQuery({
queryKey: QK.analysisMenus,
queryFn: api.analysisMenus,
})
const { data: authData } = useQuery({
queryKey: ['auth-status'],
queryFn: api.authStatus,
refetchInterval: 60_000,
})
const version = versionData?.version
const username = authData?.username
const navigate = useNavigate()
const qc = useQueryClient()
const [darkMode, setDarkMode] = useState(() => {
if (typeof document === 'undefined') return true
try {
const saved = localStorage.getItem('theme')
if (saved === 'light' || saved === 'dark') return saved === 'dark'
} catch {}
return document.documentElement.classList.contains('dark')
})
// 切换暗夜模式
const toggleDark = () => {
const next = !darkMode
setDarkMode(next)
document.documentElement.classList.toggle('dark', next)
try { localStorage.setItem('theme', next ? 'dark' : 'light') } catch {}
}
const handleLogout = async () => {
try { await api.authLogout() } catch {}
qc.invalidateQueries({ queryKey: ['auth-status'] })
navigate('/login', { replace: true })
}
// 合并内置页面 + 可见的扩展分析菜单
const analysisNav = (analysisMenus?.items ?? [])
@@ -161,27 +193,48 @@ export function Layout() {
))}
</nav>
<div className="border-t border-border px-2 py-3 space-y-0.5 shrink-0">
<div className="border-t border-border px-2 py-2.5 space-y-1 shrink-0">
{/* 登录用户 */}
{username && (
<div className="flex items-center gap-2 px-3 py-1.5 text-xs text-foreground/70">
<User className="h-3.5 w-3.5 shrink-0 text-muted" />
<span className="truncate flex-1">{username}</span>
<button
onClick={handleLogout}
className="rounded p-1 text-muted hover:text-danger hover:bg-danger/10 transition-colors"
title="退出登录"
>
<LogOut className="h-3 w-3" />
</button>
</div>
)}
{/* 暗夜模式 + 设置 */}
<div className="flex items-center gap-1 px-1">
<button
onClick={toggleDark}
className="flex items-center gap-2 flex-1 rounded-btn px-2 py-1.5 text-xs text-foreground/70 hover:bg-elevated hover:text-foreground transition-colors"
title={darkMode ? '切换为日间模式' : '切换为暗夜模式'}
>
{darkMode ? <Moon className="h-3.5 w-3.5" /> : <Sun className="h-3.5 w-3.5" />}
<span>{darkMode ? '暗夜' : '日间'}</span>
</button>
<NavLink
to="/settings"
className={({ isActive }) =>
cn(
'flex items-center justify-between gap-3 px-3 py-2 rounded-btn text-sm transition-colors duration-150 ease-smooth',
'flex items-center justify-center rounded-btn p-1.5 text-xs transition-colors',
isActive
? 'bg-elevated text-foreground font-medium'
: 'text-foreground/80 hover:bg-elevated hover:text-foreground',
? 'bg-accent/10 text-accent'
: 'text-foreground/70 hover:bg-elevated hover:text-foreground',
)
}
title="设置"
>
<span className="flex items-center gap-3">
<Settings className="h-4 w-4 shrink-0" />
<span></span>
</span>
<span className="font-mono text-[10px] text-muted/50 select-none">
{version ?? ''}
</span>
<Settings className="h-3.5 w-3.5" />
</NavLink>
</div>
</div>
</aside>
<motion.main