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相关的教程。祝大家新年快乐,万事如意。

相关推荐
JarvanMo17 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
JohnYan17 小时前
工作笔记-CodeBuddy应用探索
javascript·ai编程·aiops
恋猫de小郭17 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木17 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮17 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati17 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉17 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n17 小时前
双端 Diff 算法详解
前端·javascript·vue.js
UrbanJazzerati17 小时前
Vue 3 纯小白快速入门指南
前端·面试
雮尘17 小时前
手把手带你玩转Android gRPC:一篇搞定原理、配置与客户端开发
android·前端·grpc