Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions packages/ui/src/BasicLayout/demo/menu-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* iframe: 600
*/
import React from 'react';
import { Menu, message } from '@oceanbase/design';
import { BasicLayout, Lottie } from '@oceanbase/ui';
import Icon from '@oceanbase/icons';
import PageContainerCompleteDemo from '../../PageContainer/demo/complete';
import { ReactComponent as MonitorSvg } from '../../assets/monitor.svg';
import IconFont from './IconFont';

export default () => {
const menus = [
{
link: '/~demos/basiclayout-demo-menu-group/overview',
title: '总览',
icon: <IconFont type="overview" />,
selectedIcon: <Lottie path="/lottie/overview.json" mode="icon" loop={false} speed={3} />,
},
{
type: 'group' as const,
title: '集群管理',
children: [
{
link: '/~demos/basiclayout-demo-menu-group/cluster',
title: '集群列表',
icon: <IconFont type="tenant" />,
},
{
link: '/~demos/basiclayout-demo-menu-group/host',
title: '主机列表',
icon: <IconFont type="diagnosis" />,
},
],
},
{
type: 'group' as const,
title: '运维监控',
children: [
{
link: '/~demos/basiclayout-demo-menu-group/monitor',
title: '监控中心',
icon: <Icon component={MonitorSvg} />,
selectedIcon: <Lottie path="/lottie/monitor.json" mode="icon" loop={false} speed={3} />,
},
{
link: '/~demos/basiclayout-demo-menu-group/backup',
title: '备份恢复',
icon: <IconFont type="backup" />,
},
],
},
{
link: '/~demos/basiclayout-demo-menu-group/property',
title: '系统参数',
icon: <IconFont type="property" />,
selectedIcon: <Lottie path="/lottie/property.json" mode="icon" loop={false} speed={3} />,
},
];
const userMenu = (
<Menu
onClick={() => {
message.success('你点击了下拉菜单');
}}
>
<Menu.Item key="profile">个人设置</Menu.Item>
<Menu.Item key="modifyPassword">修改密码</Menu.Item>
<Menu.Item key="logout">退出登录</Menu.Item>
</Menu>
);
return (
<BasicLayout
logoUrl="https://mdn.alipayobjects.com/huamei_n8rchn/afts/img/A*WElAQJswckAAAAAAAAAAAAAADvSFAQ/original"
simpleLogoUrl="https://mdn.alipayobjects.com/huamei_n8rchn/afts/img/A*x1BtQ5x7_pUAAAAAAAAAAAAADvSFAQ/original"
menus={menus}
defaultSelectedKeys={['/~demos/basiclayout-demo-menu-group/overview']}
topHeader={{
welcomePath: '/welcome',
versionNoticePath: '/docs/index.html',
docsPath: '/docs/index.html',
pdfPath: '/docs/index.html',
username: 'admin',
userMenu,
showLocale: true,
appData: {
shortName: 'OCP Express',
version: '1.0.0',
releaseTime: '2022-12-30 00:00:00',
},
}}
>
<PageContainerCompleteDemo />
</BasicLayout>
);
};
4 changes: 3 additions & 1 deletion packages/ui/src/BasicLayout/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ nav:
<code src="./demo/menu-over-length.tsx" title="菜单项内容超长" description="自动省略 + tooltip。"></code>
<code src="./demo/banner.tsx" title="带顶部 banner"></code>
<code src="./demo/topHeader-icon.tsx" title="顶部导航 icon 模式"></code>
<code src="./demo/menu-group.tsx" title="菜单分组" description="与 antd Menu 一致,支持 type: 'group' 分组展示。"></code>
<code src="./demo/topHeader-title.tsx" title="带顶部标题"></code>
<code src="./demo/subSideMenu.tsx" title="带子侧边栏的二级导航"></code>
<code src="./demo/no-sider-and-custom-header.tsx" title="无侧边导航 & 自定义顶部导航"></code>
Expand Down Expand Up @@ -95,8 +96,9 @@ const App = () => {

| 参数 | 说明 | 类型 | 默认值 | 版本 |
| :-- | :-- | :-- | :-- | :-- |
| link | 菜单对应的路径或链接 | string | - | - |
| link | 菜单对应的路径或链接,type 为 `'group'` 时可省略 | string | - | - |
| title | 菜单标题 | string | - | - |
| type | 与 antd Menu 一致,设为 `'group'` 时为分组标题,不设 link,children 为该组下的菜单项 | 'group' | - | - |
| icon | 菜单 icon | ReactNode | - | - |
| selectedIcon | 菜单选中态 icon | ReactNode | - | - |
| accessible | 菜单项是否渲染,常用于权限控制和条件渲染的场景 | boolean | true | - |
Expand Down
52 changes: 37 additions & 15 deletions packages/ui/src/BasicLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import zhCN from './locale/zh-CN';
import useStyle from './style';

const { Content, Sider } = Layout;
const { SubMenu, Item } = Menu;
const { SubMenu, Item, ItemGroup } = Menu;

export interface SiderHeaderProps {
backUrl: string;
Expand All @@ -32,12 +32,14 @@ export interface SiderHeaderProps {
}

export interface MenuItem {
link: string;
link?: string;
title: string;
icon?: React.ReactNode;
selectedIcon?: React.ReactNode;
accessible?: boolean;
divider?: boolean;
/** 与 antd Menu 一致,设为 'group' 时渲染为分组标题,children 为该组下的菜单项 */
type?: 'group';
children?: MenuItem[];
}

Expand Down Expand Up @@ -142,11 +144,15 @@ const BasicLayout: React.FC<BasicLayoutProps> = ({
let keys = [];
(menuList || []).forEach(item => {
const itemChildren = item.children || [];
const childrenKeys = itemChildren.map(child => child.link);
if (childrenKeys.includes(selectedKey)) {
keys = [...keys, item.link];
if (item.type === 'group') {
keys = [...keys, ...getParentKeys(itemChildren, selectedKey)];
} else {
keys = [...keys, getParentKeys(itemChildren, selectedKey)];
const childrenKeys = itemChildren.map(child => child.link).filter(Boolean);
if (childrenKeys.includes(selectedKey)) {
keys = [...keys, item.link];
} else {
keys = [...keys, ...getParentKeys(itemChildren, selectedKey)];
}
}
});
return keys;
Expand All @@ -155,10 +161,14 @@ const BasicLayout: React.FC<BasicLayoutProps> = ({
const getFlatMenuKeys = (menuList: MenuItem[]) => {
let keys = [];
(menuList || []).forEach(item => {
if (item.children) {
keys = keys.concat(getFlatMenuKeys(item.children));
if (item.type === 'group') {
keys = keys.concat(getFlatMenuKeys(item.children || []));
} else {
if (item.children) {
keys = keys.concat(getFlatMenuKeys(item.children));
}
keys.push(item.link || '');
}
keys.push(item.link || '');
});
return keys;
};
Expand All @@ -177,9 +187,15 @@ const BasicLayout: React.FC<BasicLayoutProps> = ({

// 渲染菜单
const renderMenu = (data: MenuItem[]) => {
return data.reduce((pre, item) => {
return data.reduce((pre, item, index) => {
const { accessible = true } = item;
if (
if (item.type === 'group' && item.children?.length && accessible) {
pre.push(
<ItemGroup key={item.title ?? `group-${index}`} title={item.title}>
{renderMenu(item.children)}
</ItemGroup>
);
} else if (
item.children &&
(isNullValue(item.accessible)
? // 如果子菜单本身没有设置 accessible,但只要其 children 之一可访问,则子菜单仍然展示
Expand Down Expand Up @@ -212,7 +228,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = ({
{renderMenu(item.children)}
</SubMenu>
);
} else if (!item.children && accessible) {
} else if (!item.children && accessible && item.link) {
pre.push(
<Item
data-testid="menu.item"
Expand Down Expand Up @@ -261,9 +277,15 @@ const BasicLayout: React.FC<BasicLayoutProps> = ({

// 渲染收起菜单
const renderCollapsedMenu = (data: MenuItem[], isSubSider = false) => {
return data.reduce((pre, item) => {
return data.reduce((pre, item, index) => {
const { accessible = true } = item;
if (
if (item.type === 'group' && item.children?.length && accessible) {
pre.push(
<ItemGroup key={item.title ?? `group-${index}`} title={item.title}>
{renderMenu(item.children)}
</ItemGroup>
);
} else if (
item.children &&
(isNullValue(item.accessible)
? // 如果子菜单本身没有设置 accessible,但只要其 children 之一可访问,则子菜单仍然展示
Expand All @@ -275,7 +297,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = ({
{renderMenu(item.children)}
</SubMenu>
);
} else if (!item.children && accessible) {
} else if (!item.children && accessible && item.link) {
pre.push(
<Item
data-testid="menu.item"
Expand Down
Loading