import { useMemo } from 'react';
|
import type { ReactNode } from 'react';
|
import { App as AntdApp, Alert, Card, Space, Table, Tag } from 'antd';
|
import type { TableProps } from 'antd';
|
import AuthButton from '@/components/AuthButton';
|
import { PERMISSION } from '@/constants/permissions';
|
import { appRoutes, type AppRouteItem } from '@/router/routeConfig';
|
import './MenuPermission.scss';
|
|
interface MenuNode {
|
key: string;
|
title: string;
|
path: string;
|
permission: string;
|
type: '目录' | '菜单';
|
icon?: ReactNode;
|
children?: MenuNode[];
|
}
|
|
function toMenuData(routes: AppRouteItem[]): MenuNode[] {
|
return routes.map((route) => ({
|
key: route.path,
|
title: route.title,
|
path: route.path,
|
permission: route.permission ?? '-',
|
type: route.children?.length ? '目录' : '菜单',
|
icon: route.icon,
|
children: route.children ? toMenuData(route.children) : undefined,
|
}));
|
}
|
|
export default function MenuPermission() {
|
const { message } = AntdApp.useApp();
|
// appRoutes 与当前页面存在模块循环引用,放在渲染期读取可避免模块初始化的 TDZ
|
const dataSource = useMemo(() => toMenuData(appRoutes), []);
|
|
const columns: TableProps<MenuNode>['columns'] = [
|
{
|
title: '菜单名称',
|
dataIndex: 'title',
|
width: 240,
|
render: (value: string, record) => (
|
<span className="menu-name">
|
{record.icon}
|
<span>{value}</span>
|
</span>
|
),
|
},
|
{
|
title: '路由路径',
|
dataIndex: 'path',
|
width: 220,
|
render: (value: string) => <code className="menu-code">{value}</code>,
|
},
|
{
|
title: '权限标识',
|
dataIndex: 'permission',
|
width: 240,
|
render: (value: string) =>
|
value === '-' ? (
|
<span className="text-muted">-</span>
|
) : (
|
<code className="menu-code">{value}</code>
|
),
|
},
|
{
|
title: '类型',
|
dataIndex: 'type',
|
width: 90,
|
render: (value: MenuNode['type']) => (
|
<Tag color={value === '目录' ? 'purple' : 'blue'} bordered={false}>
|
{value}
|
</Tag>
|
),
|
},
|
{
|
title: '状态',
|
key: 'status',
|
width: 90,
|
render: () => (
|
<Tag color="green" bordered={false}>
|
启用
|
</Tag>
|
),
|
},
|
{
|
title: '操作',
|
key: 'action',
|
width: 110,
|
render: (_value, record) => (
|
<AuthButton
|
type="link"
|
size="small"
|
permission={PERMISSION.PERMISSION_EDIT}
|
onClick={() => message.info(`演示:编辑「${record.title}」的菜单配置`)}
|
>
|
编辑
|
</AuthButton>
|
),
|
},
|
];
|
|
return (
|
<div className="menu-permission-page">
|
<div className="page-header">
|
<div>
|
<h1 className="page-header__title">菜单权限</h1>
|
<p className="page-header__desc">
|
菜单由路由配置自动生成,支持两级、三级嵌套;无权限的菜单会自动从侧边栏隐藏。
|
</p>
|
</div>
|
<div className="page-header__actions">
|
<Space wrap>
|
<AuthButton
|
permission={PERMISSION.PERMISSION_EDIT}
|
onClick={() => message.info('演示:打开新增菜单弹窗')}
|
>
|
新增菜单
|
</AuthButton>
|
<AuthButton
|
type="primary"
|
permission={PERMISSION.PERMISSION_EDIT}
|
onClick={() => message.success('菜单权限配置已保存(演示)')}
|
>
|
保存配置
|
</AuthButton>
|
</Space>
|
</div>
|
</div>
|
|
<Alert
|
className="menu-permission-alert"
|
type="info"
|
showIcon
|
message="路由即菜单"
|
description="所有页面统一在 src/router/routeConfig.tsx 中配置 path、title、permission 与组件,侧边栏、面包屑以及路由守卫都会读取同一份配置。"
|
/>
|
|
<Card className="table-card" variant="borderless">
|
<Table<MenuNode>
|
rowKey="key"
|
columns={columns}
|
dataSource={dataSource}
|
pagination={false}
|
defaultExpandAllRows
|
scroll={{ x: 900 }}
|
/>
|
</Card>
|
</div>
|
);
|
}
|