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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-06 12:57:31 +08:00
parent fc69f51e23
commit d2f428ec86
+78 -25
View File
@@ -1,5 +1,6 @@
import { NavLink, Outlet } from 'react-router-dom' import { useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { NavLink, Outlet, useNavigate } from 'react-router-dom'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { ToastContainer } from '@/components/Toast' import { ToastContainer } from '@/components/Toast'
import { AlertToastContainer } from '@/components/AlertToast' import { AlertToastContainer } from '@/components/AlertToast'
@@ -10,7 +11,6 @@ import { StockAnalysisBubble } from '@/components/stock-analysis/StockAnalysisBu
import { import {
useSettings, useSettings,
usePreferences, usePreferences,
useVersion,
} from '@/lib/useSharedQueries' } from '@/lib/useSharedQueries'
import { QK } from '@/lib/queryKeys' import { QK } from '@/lib/queryKeys'
import { import {
@@ -25,6 +25,10 @@ import {
Layers3, Layers3,
Landmark, Landmark,
BookOpenCheck, BookOpenCheck,
Moon,
Sun,
User,
LogOut,
} from 'lucide-react' } from 'lucide-react'
import { Logo } from './Logo' import { Logo } from './Logo'
import { api } from '@/lib/api' import { api } from '@/lib/api'
@@ -76,14 +80,42 @@ function AIConfigBadge({ configured, model }: { configured?: boolean; model?: st
export function Layout() { export function Layout() {
// ===== 共享 hooks (替代内联 useQuery) ===== // ===== 共享 hooks (替代内联 useQuery) =====
const { data: settingsState } = useSettings() const { data: settingsState } = useSettings()
const { data: versionData } = useVersion()
const { data: prefs } = usePreferences() const { data: prefs } = usePreferences()
const { data: analysisMenus } = useQuery({ const { data: analysisMenus } = useQuery({
queryKey: QK.analysisMenus, queryKey: QK.analysisMenus,
queryFn: api.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 ?? []) const analysisNav = (analysisMenus?.items ?? [])
@@ -161,26 +193,47 @@ export function Layout() {
))} ))}
</nav> </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">
<NavLink {/* 登录用户 */}
to="/settings" {username && (
className={({ isActive }) => <div className="flex items-center gap-2 px-3 py-1.5 text-xs text-foreground/70">
cn( <User className="h-3.5 w-3.5 shrink-0 text-muted" />
'flex items-center justify-between gap-3 px-3 py-2 rounded-btn text-sm transition-colors duration-150 ease-smooth', <span className="truncate flex-1">{username}</span>
isActive <button
? 'bg-elevated text-foreground font-medium' onClick={handleLogout}
: 'text-foreground/80 hover:bg-elevated hover:text-foreground', className="rounded p-1 text-muted hover:text-danger hover:bg-danger/10 transition-colors"
) title="退出登录"
} >
> <LogOut className="h-3 w-3" />
<span className="flex items-center gap-3"> </button>
<Settings className="h-4 w-4 shrink-0" /> </div>
<span></span> )}
</span>
<span className="font-mono text-[10px] text-muted/50 select-none"> {/* 暗夜模式 + 设置 */}
{version ?? ''} <div className="flex items-center gap-1 px-1">
</span> <button
</NavLink> 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-center rounded-btn p-1.5 text-xs transition-colors',
isActive
? 'bg-accent/10 text-accent'
: 'text-foreground/70 hover:bg-elevated hover:text-foreground',
)
}
title="设置"
>
<Settings className="h-3.5 w-3.5" />
</NavLink>
</div>
</div> </div>
</aside> </aside>