95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import {
|
|
IconAlarm,
|
|
IconBarChartHStroked,
|
|
IconBox,
|
|
IconChevronLeft,
|
|
IconHelpCircle,
|
|
IconHome,
|
|
IconMapPin,
|
|
IconSearch,
|
|
IconSetting,
|
|
IconUser,
|
|
IconExit
|
|
} from '@douyinfe/semi-icons';
|
|
import { useEffect, useState } from 'react';
|
|
import { NavLink, Outlet, useLocation } from 'react-router-dom';
|
|
import { usePlatformSession } from '../auth/AuthGate';
|
|
import { preloadRoute, scheduleIdleRoutePreloads } from '../routing/routeModules';
|
|
|
|
const navigation = [
|
|
{ to: '/monitor', label: '全局监控', icon: IconHome },
|
|
{ to: '/vehicles', label: '车辆查询', icon: IconSearch },
|
|
{ to: '/tracks', label: '轨迹回放', icon: IconMapPin },
|
|
{ to: '/history', label: '历史数据', icon: IconBarChartHStroked },
|
|
{ to: '/statistics', label: '里程查询', icon: IconBarChartHStroked },
|
|
{ to: '/alerts', label: '告警中心', icon: IconAlarm },
|
|
{ to: '/access', label: '接入管理', icon: IconBox }
|
|
];
|
|
|
|
const pageNames: Record<string, string> = {
|
|
monitor: '全局监控',
|
|
vehicles: '车辆查询',
|
|
tracks: '轨迹回放',
|
|
history: '历史数据',
|
|
statistics: '里程查询',
|
|
alerts: '告警中心',
|
|
access: '接入管理',
|
|
operations: '运维质量'
|
|
};
|
|
|
|
export function AppShell() {
|
|
const location = useLocation();
|
|
const [initialPathname] = useState(() => location.pathname);
|
|
const section = location.pathname.split('/')[1] || 'monitor';
|
|
const { session, logout } = usePlatformSession();
|
|
const roleLabel = { viewer: '只读', operator: '处置员', admin: '管理员' }[session.role];
|
|
useEffect(() => scheduleIdleRoutePreloads({ activePathname: initialPathname }), [initialPathname]);
|
|
|
|
return (
|
|
<div className="v2-shell">
|
|
<Sidebar />
|
|
<div className="v2-main">
|
|
<header className="v2-topbar">
|
|
<h1>{pageNames[section] ?? '车辆数据中台'}</h1>
|
|
<div className="v2-topbar-actions">
|
|
<button type="button" aria-label="帮助"><IconHelpCircle /></button>
|
|
<span className="v2-current-user"><IconUser /><b>{session.name}</b><small>{roleLabel}</small></span>
|
|
<button type="button" aria-label="退出登录" title="退出登录" onClick={logout}><IconExit /></button>
|
|
</div>
|
|
</header>
|
|
<main className="v2-content"><Outlet /></main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Sidebar() {
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const warmRoute = (path: string) => { void preloadRoute(path); };
|
|
|
|
return (
|
|
<aside className={`v2-sidebar${collapsed ? ' is-collapsed' : ''}`}>
|
|
<div className="v2-brand">
|
|
<span className="v2-brand-mark"><IconBox size="large" /></span>
|
|
<strong>车辆数据中台</strong>
|
|
</div>
|
|
<nav className="v2-navigation" aria-label="主导航">
|
|
{navigation.map(({ to, label, icon: Icon }) => (
|
|
<NavLink key={to} to={to} aria-label={label} title={collapsed ? label : undefined} onPointerEnter={() => warmRoute(to)} onFocus={() => warmRoute(to)} onPointerDown={() => warmRoute(to)} className={({ isActive }) => `v2-nav-item ${isActive ? 'is-active' : ''}`}>
|
|
<Icon size="large" />
|
|
<span className="v2-nav-label">{label}</span>
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
<NavLink to="/operations" aria-label="运维质量" title={collapsed ? '运维质量' : undefined} onPointerEnter={() => warmRoute('/operations')} onFocus={() => warmRoute('/operations')} onPointerDown={() => warmRoute('/operations')} className={({ isActive }) => `v2-nav-item v2-nav-operations ${isActive ? 'is-active' : ''}`}>
|
|
<IconSetting size="large" />
|
|
<span className="v2-nav-label">运维质量</span>
|
|
</NavLink>
|
|
<button className="v2-collapse" type="button" onClick={() => setCollapsed((value) => !value)} aria-label={collapsed ? '展开侧栏' : '收起侧栏'}>
|
|
<IconChevronLeft />
|
|
<span>收起</span>
|
|
</button>
|
|
</aside>
|
|
);
|
|
}
|