专做白酒的网站,企业网站需要哪些模块,dedecms模板站,如何做网站的内链和外链我们继续未完的课程。
我们已经设计完所有theme的有关逻辑和代码了。接下来就是菜单部分#xff0c;首先#xff0c;菜单分为菜单头和菜单列表#xff0c;还有收缩模式和缩略模式。为配置能用化的考虑#xff0c;我们在菜单配置方面采用了 Json 数组。而菜单本身的数据状态…我们继续未完的课程。
我们已经设计完所有theme的有关逻辑和代码了。接下来就是菜单部分首先菜单分为菜单头和菜单列表还有收缩模式和缩略模式。为配置能用化的考虑我们在菜单配置方面采用了 Json 数组。而菜单本身的数据状态和App的业务逻辑是没有关联的。所以我们尽可能的把两者隔离开。组件间的状态共享一般通过 Props 、state、 及Reducer 这么种方式。对于单层状态的上下传递我们采用 Props 和 state 就可以但对于多层级的状传递这种方式就很不合适了。我们一般采用 Reducer 来管理组件的状态。说到Reducer 一个是 React Redux , 一个是React本身中的Recuder功能。Redux是重量级的用于整合App的业务逻辑部分是最合适不过的。但是对于那么细化的封闭的组合Reducer就更加合适了。对于这个SMenu组件我们采用 Reducer 来完成整个组件的状态的管理。
菜单配置数据的设计
我们在SMenu目录下创建 menuData.jsx 文件
import DataUsageIcon from mui/icons-material/DataUsage;
import PersonIcon from mui/icons-material/Person;
import GroupAddIcon from mui/icons-material/GroupAdd;
import VerifiedUserIcon from mui/icons-material/VerifiedUser;
import FeaturedVideoIcon from mui/icons-material/FeaturedVideo;
import PasswordIcon from mui/icons-material/Password;
import VpnKeyIcon from mui/icons-material/VpnKey;
import HealthAndSafetyIcon from mui/icons-material/HealthAndSafety;
import ReplyAllIcon from mui/icons-material/ReplyAll;//菜单的测试数据
const sideMenuConfigData [{ id: init, title: 系统初始化, icon: DataUsageIcon },{ id: management, title: 用户管理, icon: GroupAddIcon },{id: userMsg, title: 角色管理, icon: PersonIcon, children: [{ id: , title: 权限管理, icon: VerifiedUserIcon },{ id: pwdMsg, title: 密码管理, icon: PasswordIcon },{ id: keyMsg, title: 私钥管理, icon: VpnKeyIcon },{ id: agentMsg, title: 权限管理, icon: HealthAndSafetyIcon },]},{ id: advMsg, title: 广告管理, icon: FeaturedVideoIcon },{ id: plyMsg, title: 评论管理, icon: ReplyAllIcon },{id: title, title: 文章管理, icon: null, children: [{ id: caogaoMsg, title: 草稿件, icon: null },{ id: newFile, title: 新建文章, icon: null },{ id: firstMsg, title: 置顶管理, icon: null },{ id: recMsg, title: 推荐管理, icon: null },{ id: classMsg, title: 类型管理, icon: null },{ id: emailMsg, title: 邮箱管理, icon: null },]},{ id: system, title: 系统设置, icon: null },{ id: userCenter, title: 个人中心, icon: null }
];export default sideMenuConfigData;设定为了最大化的保证菜单的格调和美观我们菜单最大支持到二级菜单。菜单支持 badge 数字提醒功能 支持图标。为了设计上的统一图标一定要是MUI的图标其它svg要通过 MUI 的 createSvgIcon 函数进行封装才能用于本菜单的配置。通过上面的配置可以看出组菜单有 children 项。没有这一属性的就是一级菜单。很容易分别。
我们在 SMenu 项目目录下再健一个目录 SMenu菜单目录 我取了一个同名的目录名称。用这个目录来存放SMenu的所有子组件。
创建菜单Recucer数据
我们在SMenu菜单目录下创建一个Provider文件SideMenuProvider.jsx 我们将有关的初始状态值及相关的Context 都放到这个文件 里如下所示
// SideMenuProvider.jsx
import { useReducer, createContext, useState } from react;/*** 获取菜单项的id集合, 用于初始化菜单项的徽章,本菜单的每个Item都有一个id属性用于唯一标识菜单项。* param menuConfig * returns */
function initBadge(menuConfig){let ids {};menuConfig.forEach((element) {const name element.id;ids { ...ids, [name]: 0 };if (element.children) {const children element.children;children.forEach(el {const subName el.id;ids { ...ids, [subName]: 0 };})}});return ids;
}//菜单的内部状态的初始值用react的reducer来管理, 用context来向子组件传递通信。
const initState {activeItemId: null, //当点击一个菜单项时记录活动菜单项hoverItemId: null, //当点击一个菜单项组标题时记录打开的GroupMenu的名称。open: true, //菜单项的展开模式true为展开false为折叠showDivider: true, //菜单项的分割线模式true为显示false为不显示
}const reducer (state, action) {return {...state,...action}
}export const SideMenuState createContext(initState); //菜单的内部状态
export const SideMenuBadge createContext(null); //菜单的徽章配置数据
export const DispatchMenuState createContext(null); //菜单的内部状态的更新函数
export const DispatchMenuBadge createContext(null); //菜单的徽章的更新函数
export const SideMenuData createContext([]); //菜单的配置数据/*** 菜单的上下文Context* param children * param menuData * returns */
function SideMenuProvider({ children, menuData }) {const [badge, updateBadge] useState(initBadge(menuData));const [menuState, updateMenuState] useReducer(reducer, initState);const updateBadgeHandler (id, count) {updateBadge((state) {return {...state,[id]: count}})}return (SideMenuState.Provider value{ menuState }SideMenuBadge.Provider value{badge}DispatchMenuState.Provider value{updateMenuState}DispatchMenuBadge.Provider value{updateBadgeHandler}SideMenuData.Provider value{menuData}{children}/SideMenuData.Provider/DispatchMenuBadge.Provider/DispatchMenuState.Provider/SideMenuBadge.Provider/SideMenuState.Provider)
}export default SideMenuProvider;你看我们在设计Theme的时候用的方法在这里又一次使用了。文件中我都做了想关说明了一看就能明白。是不是很顺手。我们设计了众多的Context, 那么我们就要提供相应的 Hook 使得我们可以在组件内部调用这些值。所以我们在相同的目录下再创建一个Hooks工具库_SMenuHooks.jsx
// _SMenuHooks.jsximport { useContext } from react;
import { DispatchMenuBadge, DispatchMenuState, SideMenuBadge, SideMenuData, SideMenuState } from ./SideMenuProvider; // 获取边栏菜单的状态
export function useSideMenuState() {return useContext(SideMenuState);
}// 获取边栏菜单的小红点状态
export function useSideMenuBadge() {return useContext(SideMenuBadge);
}// 更新边栏菜单小红点的工具,用法
// const update useSideMenuBadgeUpdate();
// update(menuItemId, 50)
export function useSideMenuBadgeUpdate() {return useContext(DispatchMenuBadge);
}// 更新边栏菜单工具
export function useSideMenuStateUpdate() {return useContext(DispatchMenuState);
}// 获取菜单配置项
export function useSideMenuData() {return useContext(SideMenuData);
}
至此这个Provider就创建完了。我们在SMenu项目目录下创建一个App.jsx做为这个示例的入口文件
import SideMenuTest from ./SideMenuTest;
import SideMenuProvider from ./SMenu/SideMenuProvider;
import sideMenuConfigData from ./menuData;function App() {return (SideMenuProvider menuData{sideMenuConfigData}SideMenuTest //SideMenuProvider)
}export default App;我们姑且先用 SideMenuTest /组件来代表这个测试工程先不用管它。或者留空又或者随便写点啥都行。现在再次回到菜单目录 SMenu,我们开始对菜单的组件进行设计
菜单头
创建 _SideMenuHeader.jsx 文件
import Box from mui/system/Box;
import Avatar from mui/material/Avatar;
import Typography from mui/material/Typography;
import Stack from mui/system/Stack;
import { useSideMenuState } from ./_SMenuHooks;
import { redirect } from react-router-dom;//菜单头
const SideMenuHeader ({logo, //图标title, //标题onClick //单击事件
}) {const { open } useSideMenuState();const clickEvent () {if (onClick null) {redirect(/);} else {onClick();console.log(clickEvent);}}return (Box classNamep-3 border-bottom Stackspacing{2}direction{row}justifyContentstartalignItems{center}classNamew-100Avatarsx{{width: 35,height: 35,cursor: pointer,transition: 0.2s,transform: open ? scale(1) : scale(1.2),}}src{logo}variantroundedalt{title}onClick{clickEvent}{title title.substring(0, 1).toUpperCase()}/AvatarTypography classNametext-truncate varianth5 sx{{pl: 0.5}} { title || 码蚁基地 } /Typography/Stack/Box)
};export default SideMenuHeader;组件内 className 应用的就是Bootstrap的样式 sx 是Mui 对 style 的封装。 组件 SideMenuHeader接收一个 logo文件地址、标题、和一个点击事件的回调。Logo 如果为空则以 标题的第一个字为 Logo图像。点击回调是赋于Logo的点击事件的。
数据提醒徽章
这个功能是用来显示消息的红点提示有两种方案一个是显示一个小红点一个是显示数字。大于99的显示 99的徽章。为了更好的适配菜单我们用 styled 函数对 Badge 进行了二次封。如下所示创建 _SideMenuStyledBadge.jsx
// _SideMenuStyledBadge.jsximport { styled } from mui/material/styles;
import Badge from mui/material/Badge;const StyledBadge styled(Badge)(({ theme }) ({ .MuiBadge-badge: {right: -22,top: 16,// border: 2px solid ${theme.palette.background.paper},padding: 0 4px,},
}));export default StyledBadge;一级菜单项
就是没有子菜单项的菜单项 创建文件 _SideMenuItem.jsx
import Tooltip from mui/material/Tooltip;
import ListItemIcon from mui/material/ListItemIcon;
import ListItemButton from mui/material/ListItemButton;
import ListItemText from mui/material/ListItemText;
import Avatar from mui/material/Avatar;
import SvgIcon from mui/material/SvgIcon;
import { useSideMenuBadge, useSideMenuState, useSideMenuStateUpdate } from ./_SMenuHooks;
import StyledBadge from ./_SideMenuStyledBadge;
import Badge from mui/material/Badge;
import { grey } from mui/material/colors;/*** 主菜单项组件* param title: 菜单项标题* param id: 菜单项ID* param icon: 菜单项图标* param onClick: 菜单项单击事件 * returns */
const SideMenuItem ({title, id,icon null,onClick,
}) {const {activeItemId, open} useSideMenuState();const badgeCount useSideMenuBadge();const updateMenuState useSideMenuStateUpdate();//单击事件const itemClickeEvent () {updateMenuState({ activeItemId: id });onClick(id, title, [id], [title]);}return (ListItemButtonselected{ activeItemId id }onClick{itemClickeEvent}Tooltip title{open ? null : title} arrow placementrightBadgebadgeContent{open ? 0 : badgeCount[id]}anchorOrigin{{vertical: top,horizontal: left,}}colorerrorListItemIconsx{{ svg: {transition: 0.2s,transform: open ? scale(1) : scale(1.2),},:hover, :focus: { svg:first-of-type: {transform: open ? scale(1) : scale(1.3),}},}}{icon null ? Avatarsx{{width: 30,height: 30,fontSize: 18,bgcolor:grey[700],transition: 0.2s,transform: open ? scale(1) : scale(1.2)}}variantrounded{title.substring(0, 1).toUpperCase()}/Avatar :SvgIcon component{icon} /}/ListItemIcon/Badge/TooltipStyledBadge badgeContent{badgeCount[id]} colorerrorListItemText primary{title}//StyledBadge/ListItemButton);
};export default SideMenuItem;组菜单的子菜单项
二级菜单的子菜单项的组件设计 创建文件 _SideMenuSubItem.jsx
// _SideMenuSubItem.jsximport ListItemButton from mui/material/ListItemButton;
import ListItemIcon from mui/material/ListItemIcon;
import ListItemText from mui/material/ListItemText;
import Typography from mui/material/Typography;
import Avatar from mui/material/Avatar;
import Tooltip from mui/material/Tooltip;
import Badge from mui/material/Badge;
import SvgIcon from mui/material/SvgIcon;
import CssBaseline from mui/material/CssBaseline;
import StyledBadge from ./_SideMenuStyledBadge;import { useSideMenuBadge, useSideMenuState, useSideMenuStateUpdate } from ./_SMenuHooks;/*** 子菜单项组件* param icon: 菜单项图标* param title: 菜单项标题* param id: 菜单项ID* param groupId: 菜单项组ID* param groupTitle: 菜单项组标题* param onClick: 菜单项单击事件 * returns */
function SideMenuSubItem({icon null,title,id,groupId,groupTitle,onClick
}) {const { activeItemId, open } useSideMenuState();const updateMenuState useSideMenuStateUpdate();const badgeCount useSideMenuBadge();const handleClick () {updateMenuState({ activeItemId: id });onClick(id, title, [groupId, id], [groupTitle, title])};return (ListItemButtononClick{handleClick}selected{ activeItemId id }sx{{transition: padding 0.3s,pl: open ? 5 : 2.5,}}CssBaseline /Tooltip title{open ? null : title} arrow placementrightBadgebadgeContent{open ? 0 : activeItemId groupId ? 0 : badgeCount[id]}anchorOrigin{{vertical: top,horizontal: left,}}colorerrorListItemIconsx{{ svg: {transition: 0.2s,transform: open ? scale(1) : scale(1.2),},:hover, :focus: { svg:first-of-type: {transform: open ? scale(1) : scale(1.3),}},}}{icon null ?Avatarsx{{width: 24,height: 24,fontSize: 16,transition: 0.2s,transform: open ? scale(1) : scale(1.2),}}variantrounded {title.substring(0, 1).toUpperCase()} /Avatar :SvgIcon component{icon} sx{{ fontSize: 16 }} /}/ListItemIcon/Badge/TooltipStyledBadge badgeContent{badgeCount[id]} colorerrorListItemTextprimary{Typographysx{{ display: inline }}componentspanvariantbody1colortext.secondary{title}/Typography}//StyledBadge/ListItemButton);
}export default SideMenuSubItem;菜单组
我们现这个子菜单项与 菜单级合并形成一个菜单组项创建文件_SideMenuGroup.jsx
// _SideMenuGroup.jsximport React from react;
import List from mui/material/List;
import ListItemButton from mui/material/ListItemButton;
import ListItemIcon from mui/material/ListItemIcon;
import ListItemText from mui/material/ListItemText;
import Collapse from mui/material/Collapse;
import ExpandLess from mui/icons-material/ExpandLess;
import ExpandMore from mui/icons-material/ExpandMore;
import Avatar from mui/material/Avatar;
import Badge from mui/material/Badge;
import Tooltip from mui/material/Tooltip;
import SvgIcon from mui/material/SvgIcon;
import StyledBadge from ./_SideMenuStyledBadge;
import Box from mui/material/Box;
import Stack from mui/material/Stack;
import { grey } from mui/material/colors;import { useSideMenuState, useSideMenuStateUpdate, useSideMenuBadge } from ./_SMenuHooks;
import SMenuSubItem from ./_SideMenuSubItem;function IconItem ({ open, icon, title }) {return (ListItemIconsx{{ svg: {transition: 0.2s,transform: open ? scale(1) : scale(1.2),},:hover, :focus: { svg:first-of-type: {transform: open ? scale(1) : scale(1.3),}},}}{icon null ?Avatarsx{{width: 30,height: 30,fontSize: 18,bgcolor: grey[600],transition: 0.2s,transform: open ? scale(1) : scale(1.2)}}variantrounded{title.substring(0, 1).toUpperCase()}/Avatar :SvgIcon component{icon} /}/ListItemIcon)
}/*** 含有子菜单的菜单项* param props * returns */
function SideMenuGroup({id, //菜单项的ID名称icon null, //图标title, //标题childrenData, //子菜单 onClick, //单击事件
}) {const { hoverItemId, open } useSideMenuState();const updateMenuState useSideMenuStateUpdate();const badgeCount useSideMenuBadge();const groupBadgeNumber childrenData.map((item) badgeCount[item.id]).reduce((a, b) a b, 0); const handleClick () {updateMenuState({hoverItemId: hoverItemId id ? null : id})};return (BoxListItemButton onClick{handleClick}Tooltip title{open ? null : title} arrow placementrightBadgebadgeContent{open ? 0 : hoverItemId id ? 0 : groupBadgeNumber}anchorOrigin{{vertical: top,horizontal: left,}}// variantdotcolorerrorIconItem open{open} icon{icon} title{title} //Badge/TooltipStack direction{row} justifyContent{space-between} sx{{ width: 300 }}StyledBadge badgeContent{ hoverItemId id ? null : groupBadgeNumber } colorerrorListItemText primary{title} //StyledBadge{hoverItemId id ? ExpandLess / : ExpandMore /}/Stack/ListItemButtonCollapse in{ hoverItemId id } timeoutauto unmountOnExitList componentdiv dense{true} disablePadding{childrenData undefined ? null :childrenData.map(function (itemData, index) {return SMenuSubItemicon { itemData.icon }title { itemData.title }id {itemData.id}groupId {id}groupTitle{title}onClick{onClick}key{index} /})}/List/Collapse/Box);
}export default SideMenuGroup;注意菜单组的 Badge 显示是通过计数子菜单的 badeg 来显示的尽管我们在 Badge Context 中有配置但组菜单的这个配置是不启作用的。所以上面就有了这个统计的设计
const groupBadgeNumber childrenData.map((item) badgeCount[item.id]).reduce((a, b) a b, 0); 收缩按钮的设计
这里我设计了一个收缩菜单的按钮你可以把它放在任何地方不一定是在菜单组件内可以是在整个 App 应用的任何地方都行, 创建文件_SToggleButton.jsx
import IconButton from mui/material/IconButton;
import { useSideMenuState, useSideMenuStateUpdate } from ./_SMenuHooks;/*** 菜单的展开/收起按钮* param {*} param0 * returns */
function SToggleButton({icon}) {const menuState useSideMenuState();const updateMenuState useSideMenuStateUpdate();const clickHandler () {updateMenuState({ open: !menuState.open})}return (IconButton onClick{clickHandler}{ icon }/IconButton)
}export default SToggleButton;整合所有组件
现 在就是整合所有的组件了。创建文件 SideMenu.jsx ,我一般内部组件的文件名前加一个下划线以示区分封装好的组件则不加下划线
// SideMenu.jsximport Box from mui/system/Box;
import SideMenuItem from ./_SideMenuItem;
import Divider from mui/material/Divider;
import SideMenuGroup from ./_SideMenuGroup;
import { useSideMenuData, useSideMenuState } from ./_SMenuHooks;
import { List } from mui/material;
import SideMenuHeader from ./_SideMenuHeader;/*** 菜单的主体组件* * returns */
function SideMenu({title,logo,hClick,mClick,footer,
}) {const menuData useSideMenuData();const { open } useSideMenuState();const openWidth 300;const minWidth 65;return (BoxclassNamed-flex overflow-hidden h-100elevation{1}sx{{transition: width 0.3s,width: open ? openWidth : minWidth,borderRight: 1,borderColor: divider,}} Box classNamed-flex flex-columnSideMenuHeadertitle{title}logo{logo}onClick{hClick}/Boxsx{{flex: 1,overflowY: auto,overflowX: hidden,width: open ? openWidth : minWidth,}}List sx{{width: openWidth}} { menuData.map((item, index) {const subItemsData item.children || null;if (subItemsData null) {return SideMenuItemid{item.id}title{item.title}icon{item.icon}onClick{mClick}key{index}/}return SideMenuGroupicon{item.icon}id{item.id}title{item.title}childrenData{item.children}onClick{mClick}key{index} /})}/List/Box{footer null ?null :Divider /{footer}/}/Box/Box);
}export default SideMenu;大功告成。菜单组件全部设计完成。那么如何应用呢我们下章详解。