React实例之完善布局菜单(三)

接着上篇的内容继续。规划界面布局。界面分为三个部分,左边为菜单部分,右边上部有个 80 px 高度左右的功能区,下面是主内容区。 依据这个设计,我们进行下面的步骤:

在 SMenu项目中创建一个目录: SLayout, 这个目录里我们存储各种布局,我们先来示例一种,其它的我们根据需要可以自行设计。在这个目录中创建FullPage.jsx 文件

javascript 复制代码
// SLayout/FullPage.jsx

import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';

function FullPage({ sideMenu, pageHeader , children }) {
    return (
        <Paper className='d-flex overflow-hidden position-absolute w-100 h-100 p-0 m-0'>
            <Box className='h-100'>
                {
                    sideMenu
                }
            </Box>

            <Box className='d-flex overflow-hidden position-relative flex-grow-1'>
                <Box className="d-flex flex-column overflow-auto p-0 m-0 position-relative flex-grow-1 flex-nowrap">
                    <Box
                        className="w-100 p-3 border-bottom"
                        sx={{ height: 68 }}
                    >
                       { pageHeader }
                    </Box>
                    <Box className="p-0 w-100 h-100">
                        {children }
                    </Box>
                </Box>
            </Box>
        </Paper>
    )
}

export default FullPage;

这个没什么好说的,就是主要就是利用Flex的特殊用 Bootstrap 对界面进行布局,没什么难度。

现在我们利用这个布局把所用的元素添加进来。在SMenu项目目录下创建一个测试文件: SideMenuTest.jsx :

javascript 复制代码
// SideMenuTest.jsx
import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
import Box from '@mui/material/Box';
import SideMenu from "./SMenu/SideMenu";
import SToggleButton from './SMenu/_SToggleButton';
import { useSideMenuBadgeUpdate } from './SMenu/_SMenuHooks';
import FullPage from './SLayout/FullPage';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import ToggleThemeButton from './STheme/TaggleThemeButton';

function SideMenuTest() {
    const updateBadge = useSideMenuBadgeUpdate();
    const onClickHandler = (id, title, idPath, titlePath) => {
        console.log(id, title, idPath, titlePath);
        updateBadge(id, 0);
    }

    return (
        <FullPage
            sideMenu={<SideMenu
                    title="侧边菜单测试系统"
                    logo="/logo.png"
                    hClick = {()=>{console.log("headerOnClick")}}
                    mClick={onClickHandler} />
                }
            pageHeader={
                <Stack
                    direction="row"
                    justifyContent="space-between"
                    alignItems="center"
                    spacing={2}
                    className='w-100'
                >
                    <SToggleButton icon={<VerticalSplitIcon />} />

                    <ToggleThemeButton />
                </Stack>
            }
        >
            <Box className="w-100 h-100 d-flex flex-grow-1 justify-content-center align-items-center">
                <Typography variant='h1'>欢迎来到码蚁基地</Typography>
            </Box>
        </FullPage>
    )
}

export default SideMenuTest;

上面有个按钮组件是切换 Theme 模式的,<ToggleThemeButton/>的功能很简单,就是调用我们先前设计好的 Theme 中的Hook, 这里给出一个示例,供参考。我们在 STheme目录下创建这个组件文件:ToggleThemeButton.jsx

javascript 复制代码
// STheme/TaggleThemeButton.jsx

import IconButton from '@mui/material/IconButton';
import useToggleTheme from "./useToggleThemeHook";
import { useTheme } from '@emotion/react';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';

function ToggleThemeButton() {
    const toggleSTheme = useToggleTheme();
    const theme = useTheme();

    return (
        <IconButton sx={{ ml: 1 }} onClick={() => { toggleSTheme(); }} color="inherit">
            {theme.palette.mode === 'dark' ? <Brightness7Icon /> : <Brightness4Icon />}
        </IconButton>
    )
}

export default ToggleThemeButton;

是不是很简单。

现在我们在SMenu项目目录下的 App.jsx中引入这个 SideMenuTest 组件就OK 了。如下所示:

javascript 复制代码
import SideMenuTest from "./SideMenuTest";
import SideMenuProvider from "./SMenu/SideMenuProvider";
import sideMenuConfigData from "./menuData";

function App() {
    return (
        <SideMenuProvider menuData={sideMenuConfigData}>
            <SideMenuTest />
        </SideMenuProvider>
    )
}

export default App;

现在的目录结构看起来应该是这个样子的:

你现在运行以后就可以看到一开始所示的动图的效果了。为了查看Badge的效果,我们在 SideMenuProvider中将Badge的初始化值更改一下。这里改成了 30 ,如下所示:

javascript 复制代码
/**
 * 获取菜单项的id集合, 用于初始化菜单项的徽章,本菜单的每个Item都有一个id属性,用于唯一标识菜单项。
 * @param menuConfig 
 * @returns 
 */
function initBadge(menuConfig){
    let ids = {};
    menuConfig.forEach((element) => {
        const name = element.id;
        ids = { ...ids, [name]: 30 };

        if (element.children) {
            const children = element.children;
            children.forEach(el => {
                const subName = el.id;
                ids = { ...ids, [subName]: 30 };
            })
        }
    });

    return ids;
}

注意,我在 SideMenuTest 中传入菜单项点击事件的回调中 是这样写的:

javascript 复制代码
const updateBadge = useSideMenuBadgeUpdate();
const onClickHandler = (id, title, idPath, titlePath) => {
      console.log(id, title, idPath, titlePath);
      updateBadge(id, 0); // 单击菜单项后清除 消息提示。 
  }

现在的效果应该是这样的:

现在我们已经验证了菜单的功能 ,但是让菜单和路由结合起来才是我们真正的目的。

设计路由界面

根据我之前 React Router相关的内容知识,我们来要准备一个WellCome页面,一个 Page404 页面, 还有一个无数据匹配的空页面。

在App.jsx 文件中,我们做出如下更改和配置:

javascript 复制代码
import SideMenuTest from "./SideMenuTest";
import SideMenuProvider from "./SMenu/SideMenuProvider";
import sideMenuConfigData from "./menuData";
import Typography from '@mui/material/Typography';

import {
    createBrowserRouter,
    RouterProvider,
} from "react-router-dom";
import Page404 from "./SPages/Page404";
import Wellcome from "./SPages/Wellcome";
import NoMatchRoute from "./SPages/NoMatchRoute";

const router = createBrowserRouter([
    {
        path: "/",
        element: <SideMenuTest />,
        errorElement: <Page404 />,
        children: [
            {
                path: "userCenter",
                element: <Typography variant='h1'>这是个人中心页面模块</Typography>,
            },

            {
                index: true,
                element: <Wellcome />
            },

            {
                path: "*",
                element: <NoMatchRoute />
            }
        ]
    },
]);

function App() {
    return (
        <SideMenuProvider menuData={sideMenuConfigData}>
            <RouterProvider router={router} />
        </SideMenuProvider>
    )
}

export default App;

我们配置了 Index 路由, 还配置了 * 路由,这个路由是当前路径下子路由没有匹配项时就显示这个路由页面。这几个页面我们可以随便写个组件都行,就像 上面配置 userCenter 中的 element 一样。但是为了美观,我事先设计 了几个页面直接引用也是一样的。

现在路由配置好了,那么我们就要在 SideMenuTest中做一点小小的更改,把主页面展示区改成 Router 的占位符组件, 并配置点击事件。如下所示:

javascript 复制代码
import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
import Box from '@mui/material/Box';
import SideMenu from "./SMenu/SideMenu";
import SToggleButton from './SMenu/_SToggleButton';
import { useSideMenuBadgeUpdate } from './SMenu/_SMenuHooks';
import FullPage from './SLayout/FullPage';
import Stack from '@mui/material/Stack';
import ToggleThemeButton from './STheme/TaggleThemeButton';
import { Outlet, useNavigate } from 'react-router-dom';

function SideMenuTest() {
    const navigate = useNavigate();
    const updateBadge = useSideMenuBadgeUpdate();
    const onClickHandler = (id, title, idPath, titlePath) => {
        console.log(id, title, idPath, titlePath);
        updateBadge(id, 0);
        navigate(idPath.join("/"), true);
    }

    return (
        <FullPage
            sideMenu={<SideMenu
                    title="侧边菜单测试系统"
                    logo="/logo.png"
                hClick={() => { navigate("/", true); }}
                    mClick={onClickHandler} />
                }
            pageHeader={
                <Stack
                    direction="row"
                    justifyContent="space-between"
                    alignItems="center"
                    spacing={2}
                    className='w-100'
                >
                    <SToggleButton icon={<VerticalSplitIcon />} />

                    <ToggleThemeButton />
                </Stack>
            }
        >
            <Box className="w-100 h-100 d-flex flex-grow-1 justify-content-center align-items-center">
                <Outlet />
            </Box>
        </FullPage>
    )
}

export default SideMenuTest;

现在我们的效果应该是这样的:

相当的完美是不是。 至此,你只要多加练习,就一定能熟练的掌握 React 。本教程到此全部结束。

关于我的计划

快要过年了,事比较多,时间实在抽不出来了。可能要停更好几天了。后面我会出一个系列的 Swift UI 的教程和PHP相关的教程。祝大家新年快乐,万事如意。

相关推荐
栈老师不回家35 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙41 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~2 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨2 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
帅比九日3 小时前
【HarmonyOS Next】封装一个网络请求模块
前端·harmonyos