重构了侧边栏、接口界面;接下来继续等待重构

This commit is contained in:
ILoveBingLu
2026-03-10 18:16:30 +08:00
parent ca559a094e
commit 24c9bc3ebc
8 changed files with 1999 additions and 479 deletions
+712 -19
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -18,6 +18,9 @@
"tuisong": "node scripts/push-release.js"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.9",
"@types/dompurify": "^3.0.5",
"@types/marked": "^5.0.2",
"@types/react-virtualized-auto-sizer": "^1.0.4",
@@ -162,4 +165,4 @@
"resources/**/*"
]
}
}
}
-16
View File
@@ -5,22 +5,6 @@
background: var(--bg-primary);
}
.main-layout {
flex: 1;
display: flex;
overflow: hidden;
}
.content {
flex: 1;
overflow: auto;
padding: 24px 24px 0 24px; // 移除底部 padding
&.no-overflow {
overflow: hidden; // 数据管理页面禁用外层滚动
}
}
// 更新提示悬浮卡片
.update-toast {
position: fixed;
+23 -5
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom'
import Box from '@mui/material/Box'
import TitleBar from './components/TitleBar'
import Sidebar from './components/Sidebar'
import RouteGuard from './components/RouteGuard'
@@ -459,6 +460,8 @@ function App() {
}
// 主窗口 - 完整布局
const disableContentOverflow = ['/data-management', '/settings', '/open-api'].includes(location.pathname)
return (
<div className="app-container">
<TitleBar />
@@ -481,10 +484,25 @@ function App() {
</div>
)}
<div className="main-layout">
<Box
sx={{
flex: 1,
display: 'flex',
minHeight: 0,
overflow: 'hidden',
}}
>
<Sidebar />
<main
className={`content ${['/data-management', '/settings', '/open-api'].includes(location.pathname) ? 'no-overflow' : ''}`}
<Box
component="main"
sx={{
flex: 1,
minWidth: 0,
overflow: disableContentOverflow ? 'hidden' : 'auto',
px: 3,
pt: 3,
pb: 0,
}}
>
<RouteGuard>
<Routes>
@@ -499,8 +517,8 @@ function App() {
<Route path="/chat-history/:sessionId/:messageId" element={<ChatHistoryPage />} />
</Routes>
</RouteGuard>
</main>
</div>
</Box>
</Box>
<DecryptProgressOverlay />
{downloadProgress !== null && (
<div className="download-progress-capsule">
-126
View File
@@ -1,126 +0,0 @@
.sidebar {
width: 200px;
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: 16px 0;
transition: width 0.25s ease;
position: relative;
z-index: 100;
&.collapsed {
width: 64px;
.nav-menu {
padding: 0 8px;
}
.nav-label,
.collapse-label {
display: none;
}
.nav-item {
justify-content: center;
padding: 10px;
gap: 0;
}
.collapse-btn {
justify-content: center;
padding: 10px;
gap: 0;
}
}
}
.nav-menu {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
padding: 0 8px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
border-radius: 9999px;
color: var(--text-secondary);
text-decoration: none;
transition: all 0.2s ease;
white-space: nowrap;
border: none;
background: transparent;
cursor: pointer;
font-family: inherit;
width: 100%;
&:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
&.active {
background: var(--primary);
color: white;
}
}
.nav-icon {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.nav-label {
font-size: 14px;
font-weight: 500;
}
.sidebar-footer {
padding: 12px 8px 0 8px;
border-top: 1px solid var(--border-color);
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
.collapse-btn {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
width: 100%;
padding: 10px 16px;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
border-radius: 9999px;
transition: all 0.2s ease;
margin-top: 4px;
font-family: inherit;
svg {
width: 20px;
height: 20px;
}
&:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.collapse-label {
font-size: 14px;
font-weight: 500;
}
}
+311 -114
View File
@@ -1,11 +1,48 @@
import { useState } from 'react'
import { useState, type ReactElement } from 'react'
import { NavLink, useLocation } from 'react-router-dom'
import Avatar from '@mui/material/Avatar'
import Box from '@mui/material/Box'
import Divider from '@mui/material/Divider'
import Drawer from '@mui/material/Drawer'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import ListItemButton from '@mui/material/ListItemButton'
import ListItemIcon from '@mui/material/ListItemIcon'
import Tooltip from '@mui/material/Tooltip'
import Typography from '@mui/material/Typography'
import { Home, MessageSquare, BarChart3, Users, FileText, Database, Settings, SquareChevronLeft, SquareChevronRight, Download, Aperture, Network } from 'lucide-react'
import './Sidebar.scss'
import { useAppStore } from '../stores/appStore'
const DRAWER_WIDTH = 220
const COLLAPSED_DRAWER_WIDTH = 72
type RouteItem = {
key: string
label: string
icon: ReactElement
type: 'route'
path: string
}
type ActionItem = {
key: string
label: string
icon: ReactElement
type: 'action'
onClick: () => void
}
type NavItemConfig = RouteItem | ActionItem
function Sidebar() {
const location = useLocation()
const userInfo = useAppStore(state => state.userInfo)
const [collapsed, setCollapsed] = useState(false)
const drawerWidth = collapsed ? COLLAPSED_DRAWER_WIDTH : DRAWER_WIDTH
const userDisplayName = userInfo?.nickName?.trim() || userInfo?.alias?.trim() || '未连接用户'
const userInitial = userDisplayName.slice(0, 1).toUpperCase()
const widthTransition = '220ms cubic-bezier(0.4, 0, 0.2, 1)'
const fadeTransition = '140ms ease'
const isActive = (path: string) => {
return location.pathname === path
@@ -35,122 +72,282 @@ function Sidebar() {
}
}
const navItems: NavItemConfig[] = [
{ key: 'home', label: '首页', icon: <Home size={20} />, type: 'route', path: '/home' },
{ key: 'chat', label: '聊天查看', icon: <MessageSquare size={20} />, type: 'action', onClick: openChatWindow },
{ key: 'moments', label: '朋友圈', icon: <Aperture size={20} />, type: 'action', onClick: openMomentsWindow },
{ key: 'analytics', label: '私聊分析', icon: <BarChart3 size={20} />, type: 'route', path: '/analytics' },
{ key: 'group-analytics', label: '群聊分析', icon: <Users size={20} />, type: 'action', onClick: openGroupAnalyticsWindow },
{ key: 'annual-report', label: '年度报告', icon: <FileText size={20} />, type: 'route', path: '/annual-report' },
{ key: 'export', label: '导出数据', icon: <Download size={20} />, type: 'route', path: '/export' },
{ key: 'data-management', label: '数据管理', icon: <Database size={20} />, type: 'route', path: '/data-management' },
{ key: 'open-api', label: '开放接口', icon: <Network size={20} />, type: 'route', path: '/open-api' },
]
const navItemSx = {
minHeight: 44,
width: collapsed ? 44 : '100%',
mx: collapsed ? 'auto' : 0,
px: 1.25,
py: 1,
borderRadius: collapsed ? '50%' : '9999px',
justifyContent: 'center',
color: 'var(--text-secondary)',
transition: `width 220ms cubic-bezier(0.4, 0, 0.2, 1), margin 220ms cubic-bezier(0.4, 0, 0.2, 1), border-radius 220ms cubic-bezier(0.4, 0, 0.2, 1), background-color 0.2s ease, color 0.2s ease`,
'&:hover': {
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
},
'&.Mui-selected': {
backgroundColor: 'var(--primary)',
color: '#ffffff',
},
'&.Mui-selected:hover': {
backgroundColor: 'var(--primary-hover)',
},
}
const navItemIconSx = {
minWidth: 24,
width: 24,
color: 'inherit',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}
const railContentSx = {
display: 'flex',
alignItems: 'center',
overflow: 'hidden',
width: collapsed ? '24px' : '100%',
transition: `width ${widthTransition}`,
}
const profileContentSx = {
display: 'flex',
alignItems: 'center',
overflow: 'hidden',
width: collapsed ? '36px' : '100%',
transition: `width ${widthTransition}`,
}
const createLabelSx = (maxWidth: number) => ({
minWidth: 0,
overflow: 'hidden',
whiteSpace: 'nowrap',
maxWidth: collapsed ? '0px' : `${maxWidth}px`,
opacity: collapsed ? 0 : 1,
transform: collapsed ? 'translateX(-8px)' : 'translateX(0)',
marginLeft: collapsed ? '0px' : '12px',
transition: [
`max-width ${widthTransition}`,
`opacity ${fadeTransition}`,
`transform ${widthTransition}`,
`margin-left ${widthTransition}`,
].join(', '),
})
const renderNavItem = (item: NavItemConfig) => {
const selected = item.type === 'route' ? isActive(item.path) : false
const button = (
<ListItemButton
selected={selected}
{...(item.type === 'route'
? { component: NavLink, to: item.path }
: { onClick: item.onClick })}
sx={navItemSx}
>
<Box sx={railContentSx}>
<ListItemIcon sx={navItemIconSx}>
{item.icon}
</ListItemIcon>
<Box sx={createLabelSx(132)}>
<Typography
variant="body2"
sx={{
fontSize: 14,
fontWeight: 500,
color: 'inherit',
lineHeight: 1.2,
}}
>
{item.label}
</Typography>
</Box>
</Box>
</ListItemButton>
)
return (
<ListItem key={item.key} disablePadding sx={{ display: 'block' }}>
<Tooltip title={collapsed ? item.label : ''} placement="right">
{button}
</Tooltip>
</ListItem>
)
}
return (
<aside className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
<nav className="nav-menu">
{/* 首页 */}
<NavLink
to="/home"
className={`nav-item ${isActive('/home') ? 'active' : ''}`}
title={collapsed ? '首页' : undefined}
>
<span className="nav-icon"><Home size={20} /></span>
<span className="nav-label"></span>
</NavLink>
<Drawer
variant="permanent"
sx={{
width: drawerWidth,
flexShrink: 0,
transition: 'width 0.25s ease',
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
position: 'relative',
height: '100%',
overflowX: 'hidden',
borderRight: '1px solid var(--border-color)',
backgroundColor: 'var(--bg-secondary)',
boxShadow: 'none',
px: 1,
py: 2,
transition: 'width 0.25s ease',
backdropFilter: 'blur(18px)',
},
}}
>
<Box
component="nav"
sx={{
display: 'flex',
flexDirection: 'column',
height: '100%',
minHeight: 0,
}}
>
<List disablePadding sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{navItems.map(renderNavItem)}
</List>
{/* 聊天 - 打开独立窗口 */}
<button
className="nav-item"
onClick={openChatWindow}
title={collapsed ? '聊天查看' : undefined}
>
<span className="nav-icon"><MessageSquare size={20} /></span>
<span className="nav-label"></span>
</button>
<Box sx={{ mt: 'auto', pt: 1.5 }}>
<Divider sx={{ borderColor: 'var(--border-color)', mb: 1.5 }} />
<List disablePadding sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<ListItem disablePadding sx={{ display: 'block' }}>
<Tooltip title={collapsed ? userDisplayName : ''} placement="right">
<Box
sx={{
display: 'flex',
justifyContent: 'center',
minHeight: 52,
px: 1.25,
py: 1,
color: 'var(--text-primary)',
overflow: 'hidden',
}}
>
<Box sx={profileContentSx}>
<Avatar
src={userInfo?.avatarUrl || undefined}
alt={userDisplayName}
sx={{
width: 36,
height: 36,
bgcolor: 'var(--primary)',
color: '#ffffff',
fontSize: 15,
fontWeight: 600,
flexShrink: 0,
}}
>
{userInitial}
</Avatar>
<Box sx={createLabelSx(140)}>
<Typography
variant="body2"
noWrap
sx={{
fontSize: 14,
fontWeight: 600,
color: 'var(--text-primary)',
lineHeight: 1.2,
}}
>
{userDisplayName}
</Typography>
<Typography
variant="caption"
sx={{
display: 'block',
mt: 0.35,
color: 'var(--text-tertiary)',
lineHeight: 1.2,
}}
>
</Typography>
</Box>
</Box>
</Box>
</Tooltip>
</ListItem>
{/* 朋友圈 - 打开独立窗口 */}
<button
className="nav-item"
onClick={openMomentsWindow}
title={collapsed ? '朋友圈' : undefined}
>
<span className="nav-icon"><Aperture size={20} /></span>
<span className="nav-label"></span>
</button>
<ListItem disablePadding sx={{ display: 'block' }}>
<Tooltip title={collapsed ? '设置' : ''} placement="right">
<ListItemButton
component={NavLink}
to="/settings"
selected={isActive('/settings')}
sx={navItemSx}
>
<Box sx={railContentSx}>
<ListItemIcon sx={navItemIconSx}>
<Settings size={20} />
</ListItemIcon>
<Box sx={createLabelSx(132)}>
<Typography
variant="body2"
sx={{
fontSize: 14,
fontWeight: 500,
color: 'inherit',
lineHeight: 1.2,
}}
>
</Typography>
</Box>
</Box>
</ListItemButton>
</Tooltip>
</ListItem>
{/* 私聊分析 */}
<NavLink
to="/analytics"
className={`nav-item ${isActive('/analytics') ? 'active' : ''}`}
title={collapsed ? '私聊分析' : undefined}
>
<span className="nav-icon"><BarChart3 size={20} /></span>
<span className="nav-label"></span>
</NavLink>
{/* 群聊分析 - 打开独立窗口 */}
<button
className="nav-item"
onClick={openGroupAnalyticsWindow}
title={collapsed ? '群聊分析' : undefined}
>
<span className="nav-icon"><Users size={20} /></span>
<span className="nav-label"></span>
</button>
{/* 年度报告 */}
<NavLink
to="/annual-report"
className={`nav-item ${isActive('/annual-report') ? 'active' : ''}`}
title={collapsed ? '年度报告' : undefined}
>
<span className="nav-icon"><FileText size={20} /></span>
<span className="nav-label"></span>
</NavLink>
{/* 导出 */}
<NavLink
to="/export"
className={`nav-item ${isActive('/export') ? 'active' : ''}`}
title={collapsed ? '导出数据' : undefined}
>
<span className="nav-icon"><Download size={20} /></span>
<span className="nav-label"></span>
</NavLink>
{/* 数据管理 */}
<NavLink
to="/data-management"
className={`nav-item ${isActive('/data-management') ? 'active' : ''}`}
title={collapsed ? '数据管理' : undefined}
>
<span className="nav-icon"><Database size={20} /></span>
<span className="nav-label"></span>
</NavLink>
{/* 开放接口 */}
<NavLink
to="/open-api"
className={`nav-item ${isActive('/open-api') ? 'active' : ''}`}
title={collapsed ? '开放接口' : undefined}
>
<span className="nav-icon"><Network size={20} /></span>
<span className="nav-label"></span>
</NavLink>
</nav>
<div className="sidebar-footer">
<NavLink
to="/settings"
className={`nav-item ${isActive('/settings') ? 'active' : ''}`}
title={collapsed ? '设置' : undefined}
>
<span className="nav-icon">
<Settings size={20} />
</span>
<span className="nav-label"></span>
</NavLink>
<button
className="collapse-btn"
onClick={() => setCollapsed(!collapsed)}
title={collapsed ? '展开菜单' : '收起菜单'}
>
{collapsed ? <SquareChevronRight size={18} /> : <SquareChevronLeft size={18} />}
<span className="collapse-label">{collapsed ? '展开' : '收回'}</span>
</button>
</div>
</aside>
<ListItem disablePadding sx={{ display: 'block' }}>
<Tooltip title={collapsed ? '展开菜单' : ''} placement="right">
<ListItemButton
onClick={() => setCollapsed(!collapsed)}
sx={navItemSx}
>
<Box sx={railContentSx}>
<ListItemIcon sx={navItemIconSx}>
{collapsed ? <SquareChevronRight size={18} /> : <SquareChevronLeft size={18} />}
</ListItemIcon>
<Box sx={createLabelSx(132)}>
<Typography
variant="body2"
sx={{
fontSize: 14,
fontWeight: 500,
color: 'inherit',
lineHeight: 1.2,
}}
>
{collapsed ? '展开' : '收回'}
</Typography>
</Box>
</Box>
</ListItemButton>
</Tooltip>
</ListItem>
</List>
</Box>
</Box>
</Drawer>
)
}
+946 -196
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -3,8 +3,8 @@
.settings-page {
display: flex;
flex-direction: column;
height: calc(100% + 48px);
margin: -24px;
height: calc(100% + 24px);
margin: -24px 0 0;
position: relative;
overflow: hidden;
@@ -3312,3 +3312,4 @@ to {
inset 0 -2px 10px rgba(0, 0, 0, 0.15);
}
}