mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 18:19:03 +08:00
feat(全局侧边栏): 添加全局侧边栏功能及窗口导航优化
- 新增全局侧边栏组件,支持展开/收起和快速导航功能 - 添加窗口大小调整和导航事件通信接口 - 优化窗口导航逻辑,使用软导航避免页面重载 - 调整主窗口启动逻辑,默认打开全局侧边栏
This commit is contained in:
@@ -1,15 +1,30 @@
|
||||
import { Layout, Dialog, Input, MessagePlugin } from 'tdesign-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { HashRouter, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { HashRouter, useLocation, useNavigate, Routes, Route } from 'react-router-dom'
|
||||
import { Sidebar } from './components/Sidebar'
|
||||
import { ContentArea } from './components/ContentArea'
|
||||
import { Wizard } from './components/Wizard'
|
||||
import { ThemeProvider } from './contexts/ThemeContext'
|
||||
import { GlobalSidebar } from './components/GlobalSidebar'
|
||||
|
||||
function MainContent(): React.JSX.Element {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
if (!(window as any).api) return
|
||||
const unlisten = (window as any).api.onNavigate((route: string) => {
|
||||
// 统一路径格式进行比对,防止 / 和 /home 导致重复跳转
|
||||
const currentPath = location.pathname === '/' ? '/home' : location.pathname
|
||||
const targetPath = route === '/' ? '/home' : route
|
||||
|
||||
if (currentPath !== targetPath) {
|
||||
navigate(route)
|
||||
}
|
||||
})
|
||||
return () => unlisten()
|
||||
}, [navigate, location.pathname])
|
||||
|
||||
const [wizardVisible, setWizardVisible] = useState(false)
|
||||
const [permission, setPermission] = useState<'admin' | 'points' | 'view'>('view')
|
||||
const [hasAnyPassword, setHasAnyPassword] = useState(false)
|
||||
@@ -129,7 +144,10 @@ function App(): React.JSX.Element {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<HashRouter>
|
||||
<MainContent />
|
||||
<Routes>
|
||||
<Route path="/global-sidebar" element={<GlobalSidebar />} />
|
||||
<Route path="/*" element={<MainContent />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</ThemeProvider>
|
||||
)
|
||||
|
||||
@@ -96,3 +96,40 @@ html[theme-mode='dark'] .ss-sidebar .t-menu__item.t-is-active svg:not([fill='non
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Global Sidebar Toggle Button */
|
||||
.global-sidebar-toggle {
|
||||
width: 24px;
|
||||
height: 60px;
|
||||
background-color: var(--ss-card-bg);
|
||||
border-radius: 8px 0 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
|
||||
border: 1px solid var(--ss-border-color);
|
||||
border-right: none;
|
||||
color: var(--ss-text-main);
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
animation: twinkle-animation 3s infinite ease-in-out;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.global-sidebar-toggle:hover {
|
||||
background-color: var(--ss-item-hover);
|
||||
color: var(--td-brand-color);
|
||||
}
|
||||
|
||||
@keyframes twinkle-animation {
|
||||
0%, 100% {
|
||||
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
|
||||
filter: brightness(1);
|
||||
}
|
||||
50% {
|
||||
box-shadow: -4px 0 12px var(--td-brand-color-light), 0 0 15px var(--td-brand-color-focus);
|
||||
filter: brightness(1.2);
|
||||
color: var(--td-brand-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Button, Tooltip } from 'tdesign-react'
|
||||
import {
|
||||
HomeIcon,
|
||||
ViewListIcon,
|
||||
UserAddIcon,
|
||||
ChevronRightIcon,
|
||||
ChevronLeftIcon,
|
||||
SettingIcon
|
||||
} from 'tdesign-icons-react'
|
||||
|
||||
export const GlobalSidebar: React.FC = () => {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!(window as any).api) return
|
||||
if (expanded) {
|
||||
(window as any).api.windowResize(84, 300)
|
||||
} else {
|
||||
(window as any).api.windowResize(24, 300)
|
||||
}
|
||||
}, [expanded])
|
||||
|
||||
const openMain = () => {
|
||||
if (!(window as any).api) return
|
||||
(window as any).api.openWindow({ key: 'main', route: '/' })
|
||||
}
|
||||
|
||||
const openQuickPoint = () => {
|
||||
// 统一使用 / 路径,因为主页即是积分操作页
|
||||
openMain()
|
||||
}
|
||||
|
||||
const openLeaderboard = () => {
|
||||
if (!(window as any).api) return
|
||||
(window as any).api.openWindow({ key: 'main', route: '/leaderboard' })
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`global-sidebar-container ${expanded ? 'expanded' : 'collapsed'}`}
|
||||
style={{
|
||||
height: '100vh',
|
||||
width: '84px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
overflow: 'hidden',
|
||||
background: 'transparent',
|
||||
userSelect: 'none',
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
top: 0
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
transition: 'transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||
transform: expanded ? 'translateX(0)' : 'translateX(60px)',
|
||||
}}
|
||||
>
|
||||
{/* 侧边栏内容区 */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: 'var(--ss-card-bg)',
|
||||
borderRadius: '12px 0 0 12px',
|
||||
boxShadow: '-4px 0 16px rgba(0,0,0,0.15)',
|
||||
border: '1px solid var(--ss-border-color)',
|
||||
borderRight: 'none',
|
||||
padding: '12px 8px',
|
||||
gap: '12px',
|
||||
width: '60px',
|
||||
pointerEvents: expanded ? 'auto' : 'none',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<Tooltip content="主界面" placement="left">
|
||||
<Button shape="circle" variant="text" onClick={openMain}>
|
||||
<HomeIcon size="24px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="积分操作" placement="left">
|
||||
<Button shape="circle" variant="text" onClick={openQuickPoint}>
|
||||
<UserAddIcon size="24px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="排行榜" placement="left">
|
||||
<Button shape="circle" variant="text" onClick={openLeaderboard}>
|
||||
<ViewListIcon size="24px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="设置" placement="left">
|
||||
<Button shape="circle" variant="text" onClick={() => (window as any).api.openWindow({ key: 'main', route: '/settings' })}>
|
||||
<SettingIcon size="24px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* 展开/收起手柄 */}
|
||||
<div
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="global-sidebar-toggle"
|
||||
>
|
||||
{expanded ? <ChevronRightIcon /> : <ChevronLeftIcon />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user