Files
novalon-manage-system/novalon-manage-web/src/layouts/DefaultLayout/index.tsx
T
张翔 5dc53f57cc feat: 新增监控页面、部门管理占位与单元测试
- 新增系统监控模块(在线用户、定时任务、数据监控、服务器监控、缓存监控)
- 新增部门管理占位页面
- 路由注册新增模块与懒加载
- DefaultLayout 侧边菜单与布局优化
- 新增前端单元测试与后端 RoleUpdateRequest 测试
2026-05-06 14:18:17 +08:00

124 lines
3.3 KiB
TypeScript

import { Suspense, useMemo } from 'react'
import { Outlet, useNavigate, useLocation } from 'react-router'
import { ProLayout } from '@ant-design/pro-components'
import { Spin } from 'antd'
import {
DashboardOutlined,
UserOutlined,
TeamOutlined,
MenuOutlined,
SettingOutlined,
BookOutlined,
FileOutlined,
NotificationOutlined,
AuditOutlined,
FileSearchOutlined,
WarningOutlined,
MonitorOutlined,
} from '@ant-design/icons'
import { useAppStore } from '@/stores/useAppStore'
import { useAuthStore } from '@/stores/useAuthStore'
import { usePermissionStore } from '@/stores/usePermissionStore'
import HeaderRight from './HeaderRight'
import type { MenuItem } from '@/api/menu'
const iconMap: Record<string, React.ReactNode> = {
dashboard: <DashboardOutlined />,
user: <UserOutlined />,
users: <UserOutlined />,
role: <TeamOutlined />,
roles: <TeamOutlined />,
menu: <MenuOutlined />,
menus: <MenuOutlined />,
config: <SettingOutlined />,
dict: <BookOutlined />,
file: <FileOutlined />,
files: <FileOutlined />,
notice: <NotificationOutlined />,
loginlog: <AuditOutlined />,
oplog: <FileSearchOutlined />,
exceptionlog: <WarningOutlined />,
monitor: <MonitorOutlined />,
}
interface ProLayoutRoute {
path: string
name: string
icon?: React.ReactNode
routes?: ProLayoutRoute[]
}
function convertToProRoutes(items: MenuItem[]): ProLayoutRoute[] {
return items
.filter((item) => item.type !== 'button' && item.visible !== false)
.map((item) => {
const icon = iconMap[item.icon] || iconMap[item.permission]
const route: ProLayoutRoute = {
path: item.path || `/custom/${item.id}`,
name: item.name,
icon,
}
if (item.children?.length) {
const childRoutes = convertToProRoutes(item.children)
if (childRoutes.length > 0) {
route.routes = childRoutes
}
}
return route
})
}
export default function DefaultLayout() {
const collapsed = useAppStore((s) => s.collapsed)
const toggleCollapsed = useAppStore((s) => s.toggleCollapsed)
const username = useAuthStore((s) => s.username)
const menus = usePermissionStore((s) => s.menus)
const navigate = useNavigate()
const location = useLocation()
const route = useMemo(() => ({
path: '/',
routes: [
{ path: '/dashboard', name: '仪表盘', icon: <DashboardOutlined /> },
...convertToProRoutes(menus),
],
}), [menus])
return (
<ProLayout
title="Novalon 管理系统"
logo={null}
layout="mix"
collapsed={collapsed}
onCollapse={toggleCollapsed}
fixSiderbar
fixedHeader
route={route}
location={{ pathname: location.pathname }}
menuItemRender={(item, dom) => (
<a
onClick={() => {
if (item.path) navigate(item.path)
}}
>
{dom}
</a>
)}
subMenuItemRender={(_item, dom) => <span>{dom}</span>}
headerTitleRender={(logo, title) => (
<a onClick={toggleCollapsed} style={{ cursor: 'pointer' }}>
{logo}{title}
</a>
)}
avatarProps={{
title: username || '用户',
render: () => <HeaderRight />,
}}
>
<Suspense fallback={<Spin size="large" style={{ display: 'block', margin: '100px auto' }} />}>
<Outlet />
</Suspense>
</ProLayout>
)
}