5dc53f57cc
- 新增系统监控模块(在线用户、定时任务、数据监控、服务器监控、缓存监控) - 新增部门管理占位页面 - 路由注册新增模块与懒加载 - DefaultLayout 侧边菜单与布局优化 - 新增前端单元测试与后端 RoleUpdateRequest 测试
124 lines
3.3 KiB
TypeScript
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>
|
|
)
|
|
}
|