跳到主要内容

React之antd中台框架

about antd

https://ant.design

react生态中有很多框架,其中antd是比较非常不错的。 在工作中常用它做一些管理工作。

Ant Design是阿里蚂蚁金服团队基于React开发的ui组件,主要用于中后台系统的使用。

本文基于react+antd做一个中台框架demo.

  • 不同的顶部菜单,产生不同的侧边菜单。动态生成
  • 侧边菜单的内容显示
  • 侧边手工收起
  • 顶层menu自适应

较少文字说明,可查看源代码中注解

项目目录

# tree app
app
├── node_modules/
├── package.json
├── src
│ ├── index.js //主入口文件
│ ├── routers.js //主路由配置
│ ├── assets
│ │ ├── logo.scss //图标样式(动画样式)
│ │ └── logo.png //图标
│ └── test
│ ├── test.tsx //主框架
│ ├── login.js //在框架外显示示例
│ ├── p_sider.tsx //侧边菜单生成
│ └── p_content.js //侧边菜单子菜单内容
├── tsconfig.json
├── typings.d.ts
└── yarn.lock

源文

主框架

test.tsx

import React from 'react';
import { useNavigate,Outlet } from "react-router-dom";

import {
Layout,
Menu,
theme,
MenuProps,
Flex,
} from 'antd';

import {
UserOutlined,
MailOutlined,
SettingOutlined,
AppstoreOutlined,
} from '@ant-design/icons';

import '../assets/logo.scss';

const logoPNG = require('../assets/logo.png');
const { Header, Footer } = Layout;


//----------css样式配置-------------------------------
//layout样式
const layoutStyle = {
overflow: 'hidden',
minHeight: 'calc(100vh - 16px)', //此配置,可以让则边导航全高度自适应。
};
//header样式
const headerStyle = {
overflow: 'hidden',
width: '100vw', //宽度默认自适应
minWidth:0,
display: 'flex',
alignItems: 'center',
//backgroundColor: '#0096ff',
//border: '5px dotted red',
'padding-left':'0px',
'padding-right':'0px',
'justifyContent':'space-between',
};
//顶部导航样式
const topMenuStyle ={
//flex: 'flex-shrink',
display: 'flex',
width:'inherit',
minWidth:0,
'padding-right':'25px',
//backgroundColor: '#0096ff',
//border: '5px dotted red',
'justifyContent':'flex-end',
"backgroundColor": 'inherit',
}
//title文本样式
const webTileStyleText ={
"color":"#fff",
"fontFamily":"-moz-initial",
"backgroundColor": 'inherit',
}

//----------menu配置-------------------------------
//顶部menu
const topMenuItems: MenuProps['items'] = [
{
label: 'about',
key: 'about',
icon: <MailOutlined />,
},
{
label: 'menu1',
key: 'menu1',
icon: <MailOutlined />,
},
{
label: 'menu2',
key: 'menu2',
icon: <SettingOutlined />,
children: [
{
label: '菜单1',
key: 'menu21',
icon: <MailOutlined />,
},
{
label: '菜单2',
key: 'menu22',
icon: <MailOutlined />,
},
],
},
{
label: 'sider1',
key: 'sider1',
icon: <AppstoreOutlined />,
},
{
label: 'sider2',
key: 'sider2',
icon: <AppstoreOutlined />,
},
{
label: 'sider3',
key: 'sider3',
icon: <AppstoreOutlined />,
},
{
label: 'login',
key: 'login',
icon: <UserOutlined />,
},
];

//主程序
const App: React.FC = () => {
//调用theme的useToken()获取当前主题下的Design Token
const {token}=theme.useToken()
const colorBgContainer = token.colorBgContainer
const borderRadiusLG = token.borderRadiusLG

//顶部导航click事件
const navigate = useNavigate();
const onMenuClick: MenuProps['onClick'] = (e) => {
console.log('click ', e.key);
switch (e.key) {
case "about":
navigate("/admin/about");
break;
case "menu1":
navigate("/admin/menu1");
break;
case "menu21":
navigate("/admin/menu21");
break;
case "menu22":
navigate("/admin/menu22");
break;
case "sider1":
navigate("/admin/sider/sider1");
break;
case "sider2":
navigate("/admin/sider/sider2");
break;
case "sider3":
navigate("/admin/sider/sider3");
break;
case "login":
navigate("/login");
break;
default:
navigate("/");
break;
}
};

return (
<Layout style={layoutStyle}>
{/* header配置 */}
<Header style={headerStyle}>
<a href="/"><Flex style={webTileStyleText}> <img src={logoPNG} className="App-logo" />MyWebSiteName</Flex></a>
<Menu
selectable={true}
mode="horizontal"
defaultSelectedKeys={['about']}
items={topMenuItems}
style={topMenuStyle}
onClick={onMenuClick}
/>
</Header>

{/* 显示区配置 */}
<Layout style={{ color: '#fff'}}>
<Outlet />
</Layout>

{/* 页脚配置 */}
<Footer style={{ textAlign: 'center'}}>
<div style={{ color:'red',display: 'inline'}}>Test</div> ©{new Date().getFullYear()} Created by guo-fs.com
</Footer>
</Layout>
);
};

export default App;

侧边菜单动态生成

p_sider.tsx

import React, {useEffect,useState } from 'react'; 
import { useNavigate,Outlet } from 'react-router-dom'
import { useLocation } from 'react-router-dom';

import {
MailOutlined,
RightOutlined,
LeftOutlined,
PieChartOutlined,
DesktopOutlined,
UserOutlined,
TeamOutlined,
FileOutlined,
HomeOutlined,
} from '@ant-design/icons';

import {
Layout,
Menu,
MenuProps,
Flex,
Button,
} from 'antd';

const {Sider, Content } = Layout;


//侧边sider1
const itemsSider1: MenuProps['items'] = [
{
label: 'Sider1-1',
key: 'sider1-1',
icon: <HomeOutlined />,
},
{
label: 'Sider1-2',
key: 'sider1-2',
icon: <MailOutlined />,
},
{
label: 'Sider1-3',
key: 'sider1-3',
icon: <MailOutlined />,
},
]
//侧边sider2
const itemsSider2: MenuProps['items'] = [
{
label: 'Sider2-1',
key: 'sider2-1',
icon: <MailOutlined />,
},
{
label: 'Sider2-2',
key: 'sider2-2',
icon: <MailOutlined />,
},
{
label: 'Sider2-3',
key: 'sider2-3',
icon: <MailOutlined />,
children: [
{
label: 'Sider23-1',
key: 'sider23-1',
icon: <MailOutlined />,
},
{
label: 'Sider23-2',
key: 'sider23-2',
icon: <MailOutlined />,
},
]
},
{
label: 'Sider2-4',
key: 'sider2-4',
icon: <MailOutlined />,
},
{
label: 'Sider2-5',
key: 'sider2-5',
icon: <MailOutlined />,
},
]
//侧边sider3
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
const itemsSider3: MenuItem[] = [
getItem('Option 1', '1', <PieChartOutlined />),
getItem('Option 2', '2', <DesktopOutlined />),
getItem('User', 'sub1', <UserOutlined />, [
getItem('Tom', '3',<UserOutlined />),
getItem('Bill', '4'),
getItem('Alex', '5'),
]),
getItem('Team', 'sub2', <TeamOutlined />, [getItem('Team 1', '6'), getItem('Team 2', '8')]),
getItem('Files', '9', <FileOutlined />),
];

//flex样式,用于siderMenu中的配置,底部显示折叠按扭
const flexStyle = {
backgroundColor: 'inherit',
height: 'inherit',
}


//动态生成侧边menu
const SiderMenu = () => {
//页面侧边menu单击事件。所有侧边menu的单击事件都在此配置
const navigate = useNavigate();
const onMenuClick = (e:any) => {
//console.log('click ', e.key);
switch (e.key) {
case "sider1-1": //菜单key
navigate("/admin/sider/sider1/menu1"); //跳转地址为路由中配置的url
break;
case "sider1-2": //菜单key
navigate("/admin/sider/sider1/menu11"); //跳转地址为路由中配置的url
break;
case "sider2-1":
navigate("/admin/sider/sider2/menu2");
break;
case "sider2-2":
navigate("/admin/sider/sider2/menu21");
break;
}
}

//当为侧边导航时,菜单收起配置
const [collapsed, setCollapsed] = useState(false);

//侧边导航折叠按扭配置(通过单击button实现)
const [isClick, setIsClick] = useState(true);
const [bticon, setBticon] = useState(<LeftOutlined />);
const [btname, setBtname] = useState('收起');
function onBtClick() {
if (isClick) {
setBticon(<RightOutlined />)
//setBticon(<MenuUnfoldOutlined />)
setBtname("显示")
//setBtname("")
setCollapsed(true)
setIsClick(false)
} else {
setBticon(<LeftOutlined />)
//setBticon(<MenuFoldOutlined />)
setBtname("收起")
//setBtname("")
setIsClick(true)
setCollapsed(false)
}
}


//页面侧边menu动态配置。
const location = useLocation()
const siderUrl =location.pathname //提取当前页面url路径
const [sidermenu, setSidermenu] = useState(null); //sider菜单的状态变量
useEffect(() => {
const siderType = (siderUrl.split('/'))[3]; //提取url中第2个'/'后的字串
//console.log("运行useEffect...,siderType:",siderType)
var menuconf:any= null
if (siderType == "sider1") {
menuconf = <Menu mode="inline" items={itemsSider1} onClick={onMenuClick} />
setSidermenu(menuconf)
}
if (siderType == "sider2") {
menuconf = <Menu mode="inline" items={itemsSider2} onClick={onMenuClick} />
setSidermenu(menuconf)
}
if (siderType == "sider3") {
menuconf = <Menu mode="inline" items={itemsSider3} onClick={onMenuClick} />
setSidermenu(menuconf)
}
},
[siderUrl] //依赖项为url地址的变化
);

//渲染
return (
<Layout> {/* 采用Layout可以让Sider与Content横向排列 */}

{/* 动态生成侧边menu */}
<Sider collapsible={false} collapsed={collapsed} trigger={null}>
<Flex vertical justify="space-between" style={flexStyle}> {/* 目的是在侧边底添加一个button,用于收起导航 */}
{sidermenu}
<Button icon={bticon} style={{borderRadius:"0",borderStyle:'none'}} onClick={onBtClick}>{btname}</Button>
</Flex>
</Sider>

{/* 侧边menu在单击时输出内容在如下Content中 */}
<Content style={{ margin: '0 16px'}}>
<Outlet />
</Content>

</Layout>
)
}

export default SiderMenu;

侧边菜单click内容

p_content.js

import React from 'react';

const About = () => {
return <>
<h2>这是 about 页面</h2>
</>
}

const Menu1 = () => {
return <>
这是 Menu1 页面<br/>
这是 Menu1 页面<br/>
这是 Menu1 页面<br/>
这是 Menu1 页面
</>
}

const Menu1_1 = () => {
return <>
这是 Menu1_1 页面
</>
}

const Menu2 = () => {
return <>
这是 menu2 页面
</>
}

const Menu2_1 = () => {
return <>
这是 menu2_1 页面
</>
}

const Menu21 = () => {
return <>
这是 Menu21 页面
</>
}

const Menu22 = () => {
return <>
这是 Menu22 页面
</>
}

export {
About,
Menu1,
Menu1_1,
Menu2,
Menu2_1,
Menu21,
Menu22,
}

login.js

login.js

import React from 'react';
import { useLocation } from 'react-router-dom';

const Login = () => {
const location = useLocation()
console.log(location)
console.log(location.pathname )
return <>
这是 login 页面
</>
}

export default Login;

路由配置

routers.js

import React from 'react';  
import {BrowserRouter,Routes,Route,Navigate} from 'react-router-dom'
import App from './test/test.tsx';
import {About,Menu1,Menu1_1,Menu2,Menu2_1,Menu21,Menu22} from './test/p_content.js';
import Login from './test/login.js';
import SiderMenu from './test/p_sider.tsx';

export default function Routers(){

return(
<BrowserRouter>
<Routes>
{/*路由条目。路由可以嵌套*/}
<Route path='/' element={<Navigate to="/admin/about" />} />
<Route path='/login' element={<Login/>} />
<Route path='/admin' element={<App />} >
{/* 子路由,url为: / + 子的path */}
<Route path='about' element={<About />} />
<Route path='menu1' element={<Menu1 />} />
<Route path='menu2' element={<Menu2 />} />
<Route path='menu21' element={<Menu21 />} />
<Route path='menu22' element={<Menu22 />} />
<Route path='sider/:menu' element={<SiderMenu />} >
<Route path='menu1' element={<Menu1 />} />
<Route path='menu11' element={<Menu1_1 />} />
<Route path='menu2' element={<Menu2 />} />
<Route path='menu21' element={<Menu2_1 />} />
</Route>
</Route>
<Route path = '*' element={<p>未匹配到该路由请先设置路由页面 </p>} />
</Routes>
</BrowserRouter>
)
}

入口文件

inex.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ConfigProvider,theme } from 'antd'
import Routers from './router.js';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ConfigProvider
locale={'zh_CN.UTF-8'}
theme={{
algorithm: theme.darkAlgorithm, //默认有3个theme:defaultAlgorithm,darkAlgorithm, compactAlgorithm
token: {
colorPrimary: '#00a2fb',
borderRadius: 0,
colorBgContainer: 'rgba(7, 59, 108,.6)',
//colorBgLayout:'red',
},
components: {
Layout:{
headerBg:'#03305a',
siderBg:'#001529',
footerBg:'#031322',
}
}
}}
>

<Routers />

</ConfigProvider>
);

logo.scss

.App-logo {
width: 32px;
height: 32px;
margin: 15px;
/*height: 40vmin;*/
pointer-events: none;
}
/* 可以让图标转动起来 */
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
/* 可以让图标转动起来 */
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

效果

Alt text