mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 11:20:50 +08:00
feat: refactor MainLayout component to implement sidebar collapse functionality, enhance navigation item display, and improve layout responsiveness
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
import { NavLink, Outlet } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { useAuthStore, useConfigStore, useLanguageStore, useNotificationStore, useThemeStore } from '@/stores';
|
import { useAuthStore, useConfigStore, useLanguageStore, useNotificationStore, useThemeStore } from '@/stores';
|
||||||
@@ -34,15 +34,12 @@ const compareVersions = (latest?: string | null, current?: string | null) => {
|
|||||||
|
|
||||||
export function MainLayout() {
|
export function MainLayout() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
|
||||||
const { showNotification } = useNotificationStore();
|
const { showNotification } = useNotificationStore();
|
||||||
|
|
||||||
const apiBase = useAuthStore((state) => state.apiBase);
|
const apiBase = useAuthStore((state) => state.apiBase);
|
||||||
const serverVersion = useAuthStore((state) => state.serverVersion);
|
const serverVersion = useAuthStore((state) => state.serverVersion);
|
||||||
const serverBuildDate = useAuthStore((state) => state.serverBuildDate);
|
const serverBuildDate = useAuthStore((state) => state.serverBuildDate);
|
||||||
const connectionStatus = useAuthStore((state) => state.connectionStatus);
|
const connectionStatus = useAuthStore((state) => state.connectionStatus);
|
||||||
const checkAuth = useAuthStore((state) => state.checkAuth);
|
|
||||||
const logout = useAuthStore((state) => state.logout);
|
|
||||||
|
|
||||||
const config = useConfigStore((state) => state.config);
|
const config = useConfigStore((state) => state.config);
|
||||||
const fetchConfig = useConfigStore((state) => state.fetchConfig);
|
const fetchConfig = useConfigStore((state) => state.fetchConfig);
|
||||||
@@ -54,7 +51,7 @@ export function MainLayout() {
|
|||||||
const toggleLanguage = useLanguageStore((state) => state.toggleLanguage);
|
const toggleLanguage = useLanguageStore((state) => state.toggleLanguage);
|
||||||
|
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
const [checkingConnection, setCheckingConnection] = useState(false);
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
const [checkingVersion, setCheckingVersion] = useState(false);
|
const [checkingVersion, setCheckingVersion] = useState(false);
|
||||||
|
|
||||||
const isLocal = useMemo(() => isLocalhost(window.location.hostname), []);
|
const isLocal = useMemo(() => isLocalhost(window.location.hostname), []);
|
||||||
@@ -96,22 +93,6 @@ export function MainLayout() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckConnection = async () => {
|
|
||||||
setCheckingConnection(true);
|
|
||||||
try {
|
|
||||||
const ok = await checkAuth();
|
|
||||||
if (ok) {
|
|
||||||
showNotification(t('common.connected_status'), 'success');
|
|
||||||
} else {
|
|
||||||
showNotification(t('common.disconnected_status'), 'warning');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
showNotification(`${t('notification.login_failed')}: ${error?.message || ''}`, 'error');
|
|
||||||
} finally {
|
|
||||||
setCheckingConnection(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleVersionCheck = async () => {
|
const handleVersionCheck = async () => {
|
||||||
setCheckingVersion(true);
|
setCheckingVersion(true);
|
||||||
try {
|
try {
|
||||||
@@ -141,15 +122,10 @@ export function MainLayout() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
logout();
|
|
||||||
navigate('/login', { replace: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<aside className={`sidebar ${sidebarOpen ? 'open' : ''}`}>
|
<aside className={`sidebar ${sidebarOpen ? 'open' : ''} ${sidebarCollapsed ? 'collapsed' : ''}`}>
|
||||||
<div className="brand">{t('title.abbr')}</div>
|
<div className="brand">{sidebarCollapsed ? t('title.abbr').charAt(0) : t('title.abbr')}</div>
|
||||||
<div className="nav-section">
|
<div className="nav-section">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
@@ -157,11 +133,19 @@ export function MainLayout() {
|
|||||||
to={item.path}
|
to={item.path}
|
||||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||||
onClick={() => setSidebarOpen(false)}
|
onClick={() => setSidebarOpen(false)}
|
||||||
|
title={sidebarCollapsed ? item.label : undefined}
|
||||||
>
|
>
|
||||||
{item.label}
|
{sidebarCollapsed ? item.label.charAt(0) : item.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="sidebar-toggle"
|
||||||
|
onClick={() => setSidebarCollapsed((prev) => !prev)}
|
||||||
|
title={sidebarCollapsed ? t('sidebar.expand', { defaultValue: '展开' }) : t('sidebar.collapse', { defaultValue: '收起' })}
|
||||||
|
>
|
||||||
|
{sidebarCollapsed ? '»' : '«'}
|
||||||
|
</button>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@@ -185,9 +169,6 @@ export function MainLayout() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
<Button variant="secondary" size="sm" onClick={handleCheckConnection} loading={checkingConnection}>
|
|
||||||
{t('header.check_connection')}
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" size="sm" onClick={handleRefreshAll}>
|
<Button variant="secondary" size="sm" onClick={handleRefreshAll}>
|
||||||
{t('header.refresh_all')}
|
{t('header.refresh_all')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -200,9 +181,6 @@ export function MainLayout() {
|
|||||||
<Button variant="ghost" size="sm" onClick={toggleTheme}>
|
<Button variant="ghost" size="sm" onClick={toggleTheme}>
|
||||||
{t('theme.switch')}: {theme === 'dark' ? t('theme.dark') : t('theme.light')}
|
{t('theme.switch')}: {theme === 'dark' ? t('theme.dark') : t('theme.light')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="danger" size="sm" onClick={handleLogout}>
|
|
||||||
{t('header.logout')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,25 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $spacing-lg;
|
gap: $spacing-lg;
|
||||||
transition: transform $transition-normal;
|
transition: width $transition-normal, transform $transition-normal;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 60px;
|
||||||
|
padding: $spacing-lg $spacing-sm;
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
@@ -27,6 +45,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $spacing-sm;
|
gap: $spacing-sm;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
@@ -51,6 +70,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: $spacing-sm;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: $radius-md;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background $transition-fast, color $transition-fast;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-tertiary, var(--border-color));
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-mobile) {
|
@media (max-width: $breakpoint-mobile) {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: $z-dropdown;
|
z-index: $z-dropdown;
|
||||||
|
|||||||
Reference in New Issue
Block a user