#ProLayout - Advanced Layout
ProLayout provides a standard, yet flexible, middle and backend layout, with one-click layout switching and automatic menu generation. It can be used with PageContainer to automatically generate breadcrumbs, page headers, and provide a low-cost solution to access the footer toolbar.
#Code Demo
#Enum property switch
Use a segmented control to toggle layout, contentWidth, siderMenuType, and other enum props side by side.
import {
GithubFilled,
InfoCircleFilled,
QuestionCircleFilled,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Segmented, Space, theme } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const { token } = theme.useToken();
const [layout, setLayout] = useState<ProSettings['layout']>('mix');
const [navTheme, setNavTheme] = useState<ProSettings['navTheme']>('light');
const [contentWidth, setContentWidth] = useState<'Fluid' | 'Fixed'>('Fluid');
const [siderMenuType, setSiderMenuType] = useState<'sub' | 'group'>('group');
const [pathname, setPathname] = useState('/welcome');
return (
<div
style={{
height: 700,
overflow: 'auto',
border: `1px solid ${token.colorBorderSecondary}`,
borderRadius: token.borderRadius,
}}
>
<ProLayout
{...defaultProps}
layout={layout}
navTheme={navTheme}
contentWidth={contentWidth}
siderMenuType={siderMenuType}
fixSiderbar
location={{ pathname }}
menu={{ collapsedShowGroupTitle: true }}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={() => [
<InfoCircleFilled key="info" />,
<QuestionCircleFilled key="question" />,
<GithubFilled key="github" />,
]}
menuItemRender={(item, dom) => (
<div onClick={() => setPathname(item.path || '/welcome')}>
{dom}
</div>
)}
>
<PageContainer>
<ProCard style={{ marginBlockEnd: 16 }} variant="outlined">
<Space orientation="vertical" size={12} style={{ width: '100%' }}>
<Space>
<span>layout 导航模式:</span>
<Segmented
value={layout}
onChange={(v) => setLayout(v as any)}
options={[
{ label: '侧栏 side', value: 'side' },
{ label: '顶部 top', value: 'top' },
{ label: '混合 mix', value: 'mix' },
]}
/>
</Space>
<Space>
<span>navTheme 导航主题:</span>
<Segmented
value={navTheme}
onChange={(v) => setNavTheme(v as any)}
options={[
{ label: '亮色 light', value: 'light' },
{ label: '暗色 realDark', value: 'realDark' },
]}
/>
</Space>
<Space>
<span>contentWidth 内容宽度:</span>
<Segmented
value={contentWidth}
onChange={(v) => setContentWidth(v as any)}
options={[
{ label: '流式 Fluid', value: 'Fluid' },
{ label: '定宽 Fixed', value: 'Fixed' },
]}
/>
</Space>
<Space>
<span>siderMenuType 菜单模式:</span>
<Segmented
value={siderMenuType}
onChange={(v) => setSiderMenuType(v as any)}
options={[
{ label: '子菜单 sub', value: 'sub' },
{ label: '分组 group', value: 'group' },
]}
/>
</Space>
</Space>
</ProCard>
<ProCard>
当前配置:layout=<b>{layout}</b>、navTheme=<b>{navTheme}</b>、
contentWidth=<b>{contentWidth}</b>、siderMenuType=
<b>{siderMenuType}</b>
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default Demo;
#Basic Usage
import {
CaretDownFilled,
DoubleRightOutlined,
GithubFilled,
InfoCircleFilled,
LogoutOutlined,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProCard,
ProConfigProvider,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import { css } from '@emotion/css';
import {
Button,
ConfigProvider,
Divider,
Dropdown,
Input,
Popover,
theme,
} from 'antd';
import React, { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const SOLUTIONS = {
金融行业: [
'智能风控平台',
'供应链金融',
'数字银行核心',
'保险理赔系统',
'资产管理中台',
'合规审计平台',
],
通用方案: [
'统一权限中心',
'数据可视化引擎',
'微服务治理框架',
'低代码搭建平台',
'智能客服系统',
'DevOps 工具链',
],
};
const HOT_PRODUCTS = [
{ name: 'Ant Design Pro', desc: '开箱即用的中台前端解决方案' },
{ name: '数据可视化引擎', desc: '企业级数据看板与图表分析工具' },
{ name: '微服务治理框架', desc: '基于 K8s 的服务注册与发现' },
];
const SolutionItem: React.FC<{ children: React.ReactNode }> = (props) => {
const { token } = theme.useToken();
return (
<div
className={css`
color: ${token.colorTextSecondary};
font-size: 14px;
cursor: pointer;
line-height: 22px;
margin-bottom: 8px;
&:hover {
color: ${token.colorPrimary};
}
`}
style={{
width: '33.33%',
}}
>
{props.children}
<DoubleRightOutlined
style={{
marginInlineStart: 4,
}}
/>
</div>
);
};
const SolutionCategory: React.FC<{
title: string;
items: string[];
style?: React.CSSProperties;
}> = (props) => {
const { token } = theme.useToken();
return (
<div
style={{
width: '100%',
...props.style,
}}
>
<div
style={{
fontSize: 16,
color: token.colorTextHeading,
lineHeight: '24px',
fontWeight: 500,
marginBlockEnd: 16,
}}
>
{props.title}
</div>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
}}
>
{props.items.map((item) => (
<SolutionItem key={item}>{item}</SolutionItem>
))}
</div>
</div>
);
};
const MenuCard = () => {
const { token } = theme.useToken();
return (
<div
style={{
display: 'flex',
alignItems: 'center',
}}
>
<Divider
style={{
height: '1.5em',
}}
orientation="vertical"
/>
<Popover
placement="bottom"
styles={{
root: {
width: 'calc(100vw - 24px)',
padding: '24px',
paddingTop: 8,
height: '307px',
borderRadius: '0 0 6px 6px',
},
}}
content={
<div style={{ display: 'flex', padding: '32px 40px' }}>
<div style={{ flex: 1 }}>
<SolutionCategory
title="金融行业"
items={SOLUTIONS['金融行业']}
/>
<SolutionCategory
title="通用方案"
items={SOLUTIONS['通用方案']}
style={{ marginBlockStart: 32 }}
/>
</div>
<div
style={{
width: '308px',
borderInlineStart: '1px solid ' + token.colorBorder,
paddingInlineStart: 16,
}}
>
<div
className={css`
font-size: 14px;
color: ${token.colorText};
line-height: 22px;
`}
>
热门产品
</div>
{HOT_PRODUCTS.map((product) => (
<div
key={product.name}
className={css`
border-radius: 4px;
padding: 16px;
margin-top: 4px;
display: flex;
cursor: pointer;
&:hover {
background-color: ${token.colorBgTextHover};
}
`}
>
<img src="https://gw.alipayobjects.com/zos/antfincdn/6FTGmLLmN/bianzu%25252013.svg" alt="" />
<div
style={{
marginInlineStart: 14,
}}
>
<div
className={css`
font-size: 14px;
color: ${token.colorText};
line-height: 22px;
`}
>
{product.name}
</div>
<div
className={css`
font-size: 12px;
color: ${token.colorTextSecondary};
line-height: 20px;
`}
>
{product.desc}
</div>
</div>
</div>
))}
</div>
</div>
}
>
<div
style={{
color: token.colorTextHeading,
fontWeight: 500,
cursor: 'pointer',
display: 'flex',
gap: 4,
paddingInlineStart: 8,
paddingInlineEnd: 12,
alignItems: 'center',
}}
className={css`
&:hover {
background-color: ${token.colorBgTextHover};
}
`}
>
<span> 企业级资产中心</span>
<CaretDownFilled />
</div>
</Popover>
</div>
);
};
const SearchInput = () => {
const { token } = theme.useToken();
return (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: token.colorBgTextHover,
}}
prefix={
<SearchOutlined
style={{
color: token.colorTextLightSolid,
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: token.colorPrimary,
fontSize: 24,
}}
/>
</div>
);
};
const Demo = () => {
const [settings, setSetting] = useState<Partial<ProSettings> | undefined>({
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
});
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
const [num, setNum] = useState(40);
if (typeof document === 'undefined') {
return <div />;
}
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
overflow: 'auto',
}}
>
<ProConfigProvider hashed={false}>
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body;
}}
>
<ProLayout
prefixCls="my-prefix"
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
token={{
header: {
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
},
}}
siderMenuType="group"
menu={{
collapsedShowGroupTitle: true,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
render: (props, dom) => {
return (
<Dropdown
menu={{
items: [
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
},
],
}}
>
{dom}
</Dropdown>
);
},
}}
actionsRender={(props) => {
if (props.isMobile) return [];
if (typeof window === 'undefined') return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<SearchInput key="SearchInput" />
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
headerTitleRender={(logo, title, _) => {
const defaultDom = (
<button
type="button"
style={{
background: 'none',
border: 'none',
padding: 0,
font: 'inherit',
cursor: 'pointer',
}}
>
{logo}
{title}
</button>
);
if (typeof window === 'undefined') return defaultDom;
if (document.body.clientWidth < 1400) {
return defaultDom;
}
if (_.isMobile) return defaultDom;
return (
<>
{defaultDom}
<MenuCard />
</>
);
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<div
role="button"
tabIndex={0}
onClick={() => {
setPathname(item.path || '/welcome');
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setPathname(item.path || '/welcome');
}
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer
token={{
paddingInlinePageContainerContent: num,
}}
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button
key="1"
type="primary"
onClick={() => {
setNum(num > 0 ? 0 : 40);
}}
>
主操作
</Button>,
]}
subTitle="简单的描述"
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
<SettingDrawer
pathname={pathname}
enableDarkTheme
getContainer={(e: any) => {
if (typeof window === 'undefined') return e;
return document.getElementById('test-pro-layout');
}}
settings={settings}
onSettingChange={(changeSetting) => {
setSetting(changeSetting);
}}
disableUrlParams={false}
/>
</ProLayout>
</ConfigProvider>
</ProConfigProvider>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Common layout toggles
import { GithubOutlined } from '@ant-design/icons';
import {
DefaultFooter,
PageContainer,
ProLayout,
} from '@ant-design/pro-components';
import { Switch } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const [loading, setLoading] = useState(false);
const [collapsed, setCollapsed] = useState(true);
const [menu, setMenu] = useState(true);
const [header, setHeader] = useState(true);
const [footer, setFooter] = useState(true);
const [menuHeader, setMenuHeader] = useState(true);
const [right, setRight] = useState(true);
const [pure, setPure] = useState(false);
const [collapsedButtonRender, setCollapsedButtonRender] = useState(true);
return (
<>
<Switch
checked={loading}
onChange={(e) => setLoading(e)}
style={{
margin: 8,
}}
/>
loading 状态
<Switch
checked={collapsed}
onChange={(e) => setCollapsed(e)}
style={{
margin: 8,
}}
/>
折叠layout
<Switch
checked={menu}
onChange={(e) => setMenu(e)}
style={{
margin: 8,
}}
/>
显示菜单
<Switch
checked={collapsedButtonRender}
onChange={(e) => setCollapsedButtonRender(e)}
style={{
margin: 8,
}}
/>
显示折叠按钮
<Switch
checked={header}
onChange={(e) => setHeader(e)}
style={{
margin: 8,
}}
/>
显示顶栏
<Switch
checked={menuHeader}
onChange={(e) => setMenuHeader(e)}
style={{
margin: 8,
}}
/>
显示菜单头
<Switch
checked={footer}
onChange={(e) => setFooter(e)}
style={{
margin: 8,
}}
/>
显示页脚
<Switch
checked={right}
onChange={(e) => setRight(e)}
style={{
margin: 8,
}}
/>
显示顶栏右侧
<Switch
checked={pure}
onChange={(e) => setPure(e)}
style={{
margin: 8,
}}
/>
清爽模式
<br />
<br />
<ProLayout
{...defaultProps}
style={{
height: '100vh',
}}
menuHeaderRender={menuHeader ? undefined : false}
headerRender={header ? undefined : false}
collapsedButtonRender={collapsedButtonRender ? undefined : false}
menuRender={(_, dom) => (menu ? dom : null)}
breakpoint={false}
collapsed={collapsed}
loading={loading}
onCollapse={setCollapsed}
avatarProps={{
src: 'https://avatars1.githubusercontent.com/u/8186664?s=460&v=4',
}}
location={{
pathname: '/welcome',
}}
pure={pure}
footerRender={() =>
footer ? (
<DefaultFooter
links={[
{
key: 'Ant Design Pro',
title: 'Ant Design Pro',
href: 'https://pro.ant.design',
blankTarget: true,
},
{
key: 'github',
title: <GithubOutlined />,
href: 'https://github.com/ant-design/ant-design-pro',
blankTarget: true,
},
{
key: 'Ant Design',
title: 'Ant Design',
href: 'https://ant.design',
blankTarget: true,
},
]}
copyright="2024 星辰科技体验技术部出品"
/>
) : null
}
>
<PageContainer content="欢迎使用">Hello World</PageContainer>
</ProLayout>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Navigation Modes
import {
GithubFilled,
InfoCircleFilled,
QuestionCircleFilled,
} from '@ant-design/icons';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
siderWidth={216}
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
title: '书琰',
size: 'small',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
>
<PageContainer>
<ProCard
style={{
height: '100vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
CaretDownFilled,
DoubleRightOutlined,
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { css } from '@emotion/css';
import { Divider, Input, Popover, theme } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const SOLUTIONS = {
金融行业: [
'智能风控平台',
'供应链金融',
'数字银行核心',
'保险理赔系统',
'资产管理中台',
'合规审计平台',
],
通用方案: [
'统一权限中心',
'数据可视化引擎',
'微服务治理框架',
'低代码搭建平台',
'智能客服系统',
'DevOps 工具链',
],
};
const HOT_PRODUCTS = [
{ name: 'Ant Design Pro', desc: '开箱即用的中台前端解决方案' },
{ name: '数据 可视化引擎', desc: '企业级数据看板与图表分析工具' },
{ name: '微服务治理框架', desc: '基于 K8s 的服务注册与发现' },
];
const SolutionItem: React.FC<{ children: React.ReactNode }> = (props) => {
const { token } = theme.useToken();
return (
<div
className={css`
color: ${token.colorTextSecondary};
font-size: 14px;
cursor: pointer;
line-height: 22px;
margin-bottom: 8px;
&:hover {
color: ${token.colorPrimary};
}
`}
style={{
width: '33.33%',
}}
>
{props.children}
<DoubleRightOutlined
style={{
marginInlineStart: 4,
}}
/>
</div>
);
};
const SolutionCategory: React.FC<{
title: string;
items: string[];
style?: React.CSSProperties;
}> = (props) => {
const { token } = theme.useToken();
return (
<div
style={{
width: '100%',
...props.style,
}}
>
<div
style={{
fontSize: 16,
color: token.colorTextHeading,
lineHeight: '24px',
fontWeight: 500,
marginBlockEnd: 16,
}}
>
{props.title}
</div>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
}}
>
{props.items.map((item) => (
<SolutionItem key={item}>{item}</SolutionItem>
))}
</div>
</div>
);
};
const MenuCard = () => {
const { token } = theme.useToken();
return (
<div
style={{
display: 'flex',
alignItems: 'center',
}}
>
<Divider
style={{
height: '1.5em',
}}
orientation="vertical"
/>
<Popover
placement="bottom"
styles={{
root: {
width: 'calc(100vw - 24px)',
padding: '24px',
paddingTop: 8,
height: '307px',
borderRadius: '0 0 6px 6px',
},
}}
content={
<div style={{ display: 'flex', padding: '32px 40px' }}>
<div style={{ flex: 1 }}>
<SolutionCategory
title="金融行业"
items={SOLUTIONS['金融行业']}
/>
<SolutionCategory
title="通用方案"
items={SOLUTIONS['通用方案']}
style={{ marginBlockStart: 32 }}
/>
</div>
<div
style={{
width: '308px',
borderInlineStart: '1px solid ' + token.colorBorder,
paddingInlineStart: 16,
}}
>
<div
className={css`
font-size: 14px;
color: ${token.colorText};
line-height: 22px;
`}
>
热门产品
</div>
{HOT_PRODUCTS.map((product) => (
<div
key={product.name}
className={css`
border-radius: 4px;
padding: 16px;
margin-top: 4px;
display: flex;
cursor: pointer;
&:hover {
background-color: ${token.colorBgTextHover};
}
`}
>
<img src="https://gw.alipayobjects.com/zos/antfincdn/6FTGmLLmN/bianzu%25252013.svg" alt="" />
<div
style={{
marginInlineStart: 14,
}}
>
<div
className={css`
font-size: 14px;
color: ${token.colorText};
line-height: 22px;
`}
>
{product.name}
</div>
<div
className={css`
font-size: 12px;
color: ${token.colorTextSecondary};
line-height: 20px;
`}
>
{product.desc}
</div>
</div>
</div>
))}
</div>
</div>
}
>
<div
style={{
color: token.colorTextHeading,
fontWeight: 500,
cursor: 'pointer',
display: 'flex',
gap: 4,
paddingInlineStart: 8,
paddingInlineEnd: 12,
alignItems: 'center',
}}
className={css`
&:hover {
background-color: ${token.colorBgTextHover};
}
`}
>
<span> 企业级资产中心</span>
<CaretDownFilled />
</div>
</Popover>
</div>
);
};
const Demo = () => {
const settings: ProSettings | undefined = {
fixSiderbar: true,
layout: 'top',
splitMenus: true,
};
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuItemRender={(item, dom) => (
<div
role="button"
tabIndex={0}
onClick={() => {
setPathname(item.path || '/welcome');
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setPathname(item.path || '/welcome');
}
}}
>
{dom}
</div>
)}
headerTitleRender={(logo, title, _) => {
const defaultDom = (
<button
type="button"
style={{
background: 'none',
border: 'none',
padding: 0,
font: 'inherit',
cursor: 'pointer',
}}
onClick={() => console.log('headerTitle clicked')}
>
{logo}
{title}
</button>
);
if (
typeof document === 'undefined' ||
document.body.clientWidth < 1400
) {
return defaultDom;
}
if (_.isMobile) return defaultDom;
return (
<>
{defaultDom}
<MenuCard />
</>
);
}}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '100vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const settings: ProSettings | undefined = {
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
};
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '100vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProCard,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [settings, setSetting] = useState<Partial<ProSettings> | undefined>({
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
});
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
token={{
header: {
colorBgHeader: '#292f33',
colorHeaderTitle: '#fff',
colorTextMenu: '#dfdfdf',
colorTextMenuSecondary: '#dfdfdf',
colorTextMenuSelected: '#fff',
colorBgMenuItemSelected: '#22272b',
colorTextMenuActive: 'rgba(255,255,255,0.85)',
colorTextRightActionsItem: '#dfdfdf',
},
colorTextAppListIconHover: '#fff',
colorTextAppListIcon: '#dfdfdf',
sider: {
colorMenuBackground: '#fff',
colorMenuItemDivider: '#dfdfdf',
colorBgMenuItemHover: '#f6f6f6',
colorTextMenu: '#595959',
colorTextMenuSelected: '#242424',
colorTextMenuActive: '#242424',
},
}}
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: (
<div
style={{
color: '#dfdfdf',
}}
>
书琰
</div>
),
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(57,62,67,1)',
color: '#fff',
}}
prefix={
<SearchOutlined
style={{
color: '#dfdfdf',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
{...settings}
>
<PageContainer
breadcrumb={{
routes: [],
}}
onBack={() => window.history.back()}
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
<SettingDrawer
pathname={pathname}
enableDarkTheme
getContainer={() => document.getElementById('test-pro-layout')}
settings={settings}
onSettingChange={(changeSetting) => {
setSetting(changeSetting);
}}
disableUrlParams={false}
/>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Menu Configuration
import {
CrownFilled,
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
SmileFilled,
TabletFilled,
} from '@ant-design/icons';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { useState } from 'react';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<ProLayout
collapsed={true}
collapsedButtonRender={false}
layout="side"
route={{
routes: [
{
path: '/welcome',
name: '欢迎',
icon: <SmileFilled />,
component: './Welcome',
},
{
path: '/admin',
name: '管理',
icon: <CrownFilled />,
access: 'canAdmin',
component: './Admin',
},
{
path: '/list',
name: '列表',
icon: <TabletFilled />,
access: 'canAdmin',
component: './Admin',
},
],
}}
location={{
pathname,
}}
menu={{
type: 'group',
collapsedShowTitle: true,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<p
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
Power by Ant Design
</p>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
>
<PageContainer
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
CrownOutlined,
InfoCircleOutlined,
MergeCellsOutlined,
QuestionCircleOutlined,
TabletOutlined,
UserOutlined,
} from '@ant-design/icons';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Button, Result } from 'antd';
import { useState } from 'react';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [pathname, setPathname] = useState('/welcome');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
fixSiderbar
route={{
path: '/',
routes: [
{
path: '/admin',
name: '管理页',
icon: <CrownOutlined />,
access: 'canAdmin',
component: './Admin',
routes: [
{
path: '/admin/sub-page1',
name: '一级页面',
icon: <CrownOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page2',
name: '二级页面',
icon: <CrownOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page3',
name: '三级页面',
icon: <CrownOutlined />,
component: './Welcome',
},
],
},
{
name: '列表页',
icon: <TabletOutlined />,
path: '/list',
component: './ListTableList',
routes: [
{
path: '/list/sub-page2',
name: '二级列表页面',
icon: <CrownOutlined />,
component: './Welcome',
},
{
path: '/list/sub-page3',
name: '三级列表页面',
icon: <CrownOutlined />,
component: './Welcome',
},
],
},
],
}}
location={{
pathname,
}}
waterMarkProps={{
content: 'Pro Layout',
}}
avatarProps={{
icon: <UserOutlined />,
size: 'small',
title: '书琰',
}}
actionsRender={() => [
<InfoCircleOutlined key="InfoCircleOutlined" />,
<QuestionCircleOutlined key="QuestionCircleOutlined" />,
<MergeCellsOutlined key="MergeCellsOutlined" />,
]}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<p
style={{
textAlign: 'center',
color: 'rgba(0,0,0,0.6)',
paddingBlockStart: 12,
}}
>
Power by Ant Design
</p>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
>
<PageContainer>
<ProCard
style={{
height: '120vh',
minHeight: 600,
}}
>
<Result
status="404"
style={{
height: '100%',
}}
title="Hello World"
subTitle="Sorry, you are not authorized to access this page."
extra={<Button type="primary">Back Home</Button>}
/>
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { PlusCircleFilled, SearchOutlined } from '@ant-design/icons';
import type { MenuDataItem } from '@ant-design/pro-components';
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import { Input, Space } from 'antd';
import { useState } from 'react';
import complexMenu from './complex-menu';
const filterByMenuData = (
data: MenuDataItem[],
keyWord: string,
): MenuDataItem[] =>
data
.map((item) => {
if (item.name?.includes(keyWord)) {
return { ...item };
}
const children = filterByMenuData(item.children || [], keyWord);
if (children.length > 0) {
return { ...item, children };
}
return undefined;
})
.filter((item) => item) as MenuDataItem[];
const loopMenuItem = (menus: any[]): MenuDataItem[] =>
menus.map(({ icon: _icon, routes, ...item }) => ({
...item,
children: routes && loopMenuItem(routes),
}));
const Demo = () => {
const [keyWord, setKeyWord] = useState('');
return (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/home/overview',
}}
menuExtraRender={({ collapsed }) =>
!collapsed && (
<Space
style={{
marginBlockStart: 16,
}}
align="center"
>
<Input
style={{
borderRadius: 4,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
onPressEnter={(e) => {
setKeyWord((e.target as HTMLInputElement).value);
}}
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</Space>
)
}
menuDataRender={() => loopMenuItem(complexMenu)}
postMenuData={(menus) => filterByMenuData(menus || [], keyWord)}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const settings: ProSettings | undefined = {
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
};
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '100vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { PageContainer, ProLayout } from '@ant-design/pro-components';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/config/template/new',
}}
route={{
routes: [
{
path: '/config',
name: '配置中心',
routes: [
{
path: 'product',
name: '产品配置',
indexRoute: {
component: 'ConfigProduct/index',
},
routes: [
{
path: 'new',
component: 'ConfigProduct/NewConfig',
},
{
path: 'edit/:productKey',
component: 'ConfigProduct/NewConfig',
},
{
path: 'detail/:productKey',
component: 'ConfigProduct/Detail',
},
],
},
{
path: 'productManage',
name: '产品管理',
indexRoute: {
component: 'ConfigProductAll/index',
},
routes: [
{
path: 'detail/:productKey',
component: 'ConfigProductAll/Detail',
},
],
},
{
path: 'template',
name: '产品模板管理',
indexRoute: {
component: 'ConfigTemplate/index',
},
routes: [
{
path: 'new',
component: 'ConfigTemplate/NewConfig',
},
{
path: 'edit/:templateKey',
component: 'ConfigTemplate/NewConfig',
},
{
path: 'detail/:templateKey',
component: 'ConfigTemplate/Detail',
},
],
},
{
path: 'configItem',
name: '配置项模板管理',
indexRoute: {
component: 'ConfigItem/index',
},
routes: [
{
path: 'new',
component: 'ConfigItem/NewConfig',
},
{
path: 'edit/:productKey',
component: 'ConfigItem/NewConfig',
},
{
path: 'detail/:productKey',
component: 'ConfigItem/Detail',
},
],
},
{
path: 'meta',
name: '元数据管理',
component: 'ConfigMeta',
},
],
},
{
path: 'asset',
name: '资产',
routes: [
{
path: 'query',
name: '资产查询',
component: 'Asset',
},
{
path: 'collateral',
name: '抵押查询',
component: 'Collateral',
},
],
},
{
path: 'bill',
name: '账单',
routes: [
{
path: 'billNo',
name: '账单编号',
component: 'BillNo',
},
{
path: 'bill',
name: '账单查询',
component: 'Bill',
},
{
path: 'billItem',
name: '账单条目',
component: 'BillItem',
},
],
},
{
path: 'cif',
name: 'CIF',
routes: [
{
path: 'bankAccount',
name: '绑卡信息',
component: 'CifBankAccount',
},
{
path: 'userGroup',
name: '查询 Group',
component: 'CifUserGroup',
},
{
path: 'userId',
name: '查询 ID',
component: 'CifUserId',
},
{
path: 'newInstitution',
name: '新增机构',
indexRoute: {
component: 'CifNewInstitution/index',
},
routes: [
{
path: 'new',
component: 'CifNewInstitution/ApplyNew',
},
{
path: 'bind/:id',
component: 'CifNewInstitution/BindAccount',
},
],
},
],
},
{
path: 'tools',
name: '小工具',
routes: [
{
path: 'ttsql',
name: 'MySQL转BlinkTT流表',
component: 'ToolTT',
},
],
},
],
}}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Open All Menus by Default
Set defaultOpenAll on menu to expand every submenu on first render.
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import complexMenu from './complex-menu';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/data_hui/data_hui2',
}}
collapsed={false}
collapsedButtonRender={false}
route={{
routes: complexMenu,
}}
menu={{ defaultOpenAll: true }}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Always expand all submenus
After toggling collapse repeatedly, defaultOpenAll may stop matching user intent. Set ignoreFlatMenu on menu to ignore manually collapsed state and keep the “expand all” behavior. This runs before render, so it does not apply to async menus.
import { PageContainer, ProLayout } from '@ant-design/pro-components';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/data_hui/data_hui2',
}}
collapsed={false}
collapsedButtonRender={false}
route={{
routes: [
{
path: '/home',
name: '首页',
locale: 'menu.home',
routes: [
{
path: '/home/overview',
name: '概述',
hideInMenu: true,
locale: 'menu.home.overview',
},
{
path: '/home/search',
name: '搜索',
hideInMenu: true,
locale: 'menu.home.search',
},
],
},
{
path: '/data_hui',
name: '汇总数据',
locale: 'menu.data_hui',
routes: [
{
collapsed: true,
menuName: '域买家维度交易',
name: '域买家维度交易',
path: '/xx',
routes: [
{
id: 2,
name: '月表',
path: '/data_hui2',
},
{
name: '日表',
path: '/data_hui3?tableName=adm_rk_cr_tb_trv_byr_ds&tableSchema=alifin_odps_birisk',
},
],
},
{
name: '维度交易',
path: '/',
routes: [
{
name: '月表',
path: '/data_hui4',
},
{
name: '日表',
key: 'tableName=adm_rk_cr_tb_trv_byr_ds&tableSchema=alifin_odps_birisk',
path: '/data_hui5',
},
],
},
],
},
],
}}
menu={{
defaultOpenAll: true,
ignoreFlatMenu: true,
}}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Load Menu from Server
ProLayout provides a powerful menu, but this necessarily encapsulates a lot of behavior, leading to dissatisfaction for users who need some special logic. So we provide a number of APIs that are expected to satisfy the vast majority of our clients in this way.
The main APIs used to load menu from the server are menuDataRender and menuRender, menuDataRender controls the current menu data and menuRender controls the menu's dom node.
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import { Button, Switch } from 'antd';
import { useRef, useState } from 'react';
import customMenuDate from './custom-menu';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
let serviceData: any[] = customMenuDate;
const Demo = () => {
const actionRef = useRef<{
reload: () => void;
}>();
const [toggle, setToggle] = useState(false);
return (
<>
<ProLayout
style={{
height: '100vh',
}}
actionRef={actionRef}
suppressSiderWhenMenuEmpty={toggle}
menu={{
request: async () => {
await waitTime(2000);
return serviceData;
},
}}
location={{
pathname: '/welcome/welcome',
}}
>
<PageContainer content="欢迎使用">
<div>
当从服务器获取的菜单为空时隐藏Sider:
<Switch checked={toggle} onChange={setToggle} />
</div>
Hello World
<Button
style={{
margin: 8,
}}
onClick={() => {
serviceData = customMenuDate;
actionRef.current?.reload();
}}
>
刷新菜单
</Button>
<Button
style={{
margin: 8,
}}
onClick={() => {
serviceData = [];
actionRef.current?.reload();
}}
>
刷新菜单(空数据)
</Button>
</PageContainer>
</ProLayout>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Customize Menu Content
With menuItemRender, subMenuItemRender, title, logo, menuHeaderRender you can customize the menu style very easily. If you are really not satisfied, you can use menuRender to fully customize it.
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import defaultProps from './_defaultProps';
const Demo = () => {
return (
<div
style={{
height: '100vh',
}}
>
<ProLayout
menuItemRender={(item, dom) => (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
}}
>
pre {dom}
</div>
)}
subMenuItemRender={(_, dom) => (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
}}
>
pre {dom}
</div>
)}
title="Remax"
logo="https://gw.alipayobjects.com/mdn/rms_b5fcc5/afts/img/A*1NHAQYduQiQAAAAAAAAAAABkARQnAQ"
menuHeaderRender={(logo, title) => (
<div
id="customize_menu_header"
style={{
height: '32px',
display: 'flex',
alignItems: 'center',
gap: 8,
}}
onClick={() => {
window.open('https://remaxjs.org/');
}}
>
{logo}
{title}
</div>
)}
{...defaultProps}
location={{
pathname: '/welcome',
}}
>
<PageContainer content="欢迎使用">Hello World</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Custom Footer
ProLayout does not provide footer by default, if you want to have the same style as Pro official website, you need to introduce a footer by yourself.
import {
DefaultFooter,
PageContainer,
ProLayout,
} from '@ant-design/pro-components';
import defaultProps from './_defaultProps';
const Demo = () => (
<ProLayout
{...defaultProps}
style={{
height: '100vh',
}}
breakpoint={false}
collapsed
location={{
pathname: '/welcome',
}}
footerRender={() => (
<DefaultFooter
links={[
{
key: 'procomponents',
title: 'ProComponents',
href: 'https://procomponents.ant.design/',
},
{
key: 'antd',
title: 'Ant Design',
href: 'https://ant.design/',
},
]}
copyright="ProComponents 页脚示例"
/>
)}
>
<PageContainer content="欢迎使用">Hello World</PageContainer>
</ProLayout>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Alert, Button, Input, Space } from 'antd';
import React, { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const settings: ProSettings | undefined = {
layout: 'mix',
splitMenus: true,
};
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<>
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
token={{
header: {
heightLayoutHeader: 108,
},
}}
headerRender={(props, defaultDom) => (
<>
<Alert
title={
<div
style={{
color: 'white',
}}
>
本网站提供的部分服务在你当前浏览器中无法使用,建议你更换为
Chrome 浏览器查看本网站。
</div>
}
icon={
<InfoCircleFilled
style={{
color: 'white',
}}
/>
}
banner
style={{
backgroundColor: 'black',
}}
action={
<Button
type="text"
style={{
color: 'white',
}}
>
查看详情
</Button>
}
/>
{React.cloneElement(defaultDom as any, {
style: {
height: '56px',
lineHeight: '56px',
},
})}
</>
)}
footerRender={() => (
<Space
style={{
height: 64,
display: 'flex',
justifyContent: 'end',
alignItems: 'center',
marginInlineEnd: 24,
}}
>
<Button key="1">上一步</Button>
<Button key="2" type="primary">
保存
</Button>
</Space>
)}
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Layout Nesting & Navigation
import { UserOutlined } from '@ant-design/icons';
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import complexMenu from './complex-menu';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/home',
}}
fixSiderbar={false}
collapsedButtonRender={false}
collapsed={false}
iconfontUrl="//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js"
route={{
routes: [
{
path: '/home',
name: '收藏',
icon: 'icon-shoucang1',
},
{
path: '/home/overview',
name: 'FaceBook',
icon: 'icon-facebook',
},
{
path: '/home/search',
name: 'Twitter',
icon: 'icon-twitter',
},
],
}}
headerRender={false}
>
<ProLayout
location={{
pathname: '/home/overview',
}}
fixSiderbar={false}
route={{
routes: complexMenu,
}}
style={{
height: '400px',
}}
avatarProps={{
icon: <UserOutlined />,
}}
menuHeaderRender={false}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
PageContainer,
ProBreadcrumb,
ProLayout,
} from '@ant-design/pro-components';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/admin/process/edit/123',
}}
layout="mix"
ErrorBoundary={false}
headerContentRender={() => <ProBreadcrumb />}
breadcrumbRender={(routers = []) => [
{
path: '/',
title: '主页',
},
{
path: '/',
title: '测试页',
},
...routers,
]}
menuDataRender={() => [
{
path: '/welcome',
name: '欢迎',
},
{
path: '/admin',
name: '管理',
children: [
{
name: '申请单列表',
path: '/admin/process',
},
{
name: '申请单详情',
path: '/admin/process/detail/:id',
hideInMenu: true,
},
{
name: '编辑申请单',
path: '/admin/process/edit/:id',
hideInMenu: true,
},
{
name: '添加申请单',
path: '/admin/process/add',
hideInMenu: true,
},
],
},
]}
>
<PageContainer content="欢迎使用" breadcrumbRender={false}>
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
CrownOutlined,
LeftOutlined,
SmileOutlined,
UserOutlined,
} from '@ant-design/icons';
import { ProLayout } from '@ant-design/pro-components';
import { Button, Result, Space, Tag } from 'antd';
import { useState } from 'react';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const defaultProps = {
routes: [
{
path: '/admin/sub-page1',
name: '一级页面',
icon: <CrownOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page2',
name: '二级页面',
icon: <UserOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page3',
name: '三级页面',
icon: <SmileOutlined />,
component: './Welcome',
},
],
};
const defaultHomeProps = {
routes: [
{
path: '/',
name: '首页',
icon: <CrownOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page1',
name: '详情页',
icon: <SmileOutlined />,
component: './Welcome',
},
],
};
const Demo = () => {
const [pathname, setPathname] = useState('/admin/sub-page1');
return (
<>
<ProLayout
route={pathname === '/' ? defaultHomeProps : defaultProps}
location={{
pathname,
}}
logoStyle={{
backgroundColor: '#eee',
}}
menuHeaderRender={(logo, title, props) => {
if (pathname === '/') {
return (
<a>
{logo}
{title}
</a>
);
}
if (props?.collapsed) {
return (
<Space>
<LeftOutlined
style={{
fontSize: 24,
}}
/>
</Space>
);
}
return (
<Space orientation="vertical">
<Button
size="small"
icon={<LeftOutlined />}
onClick={() => {
setPathname('/');
}}
>
返回应用列表
</Button>
<b>alipay_dev_gzone</b>
<span>Creation Time 2017-01-10</span>
<Tag color="warning">运行中</Tag>
</Space>
);
}}
fixSiderbar
headerRender={() => {
return null;
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
avatarProps={{
icon: <UserOutlined />,
}}
>
<div
style={{
height: '120vh',
minHeight: 600,
}}
>
<Result
status="404"
style={{
height: '100%',
background: '#fff',
}}
icon={
<img
src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*aPIBS5gRPu4AAAAAAAAAAAAAARQnAQ"
alt="404"
/>
}
title="Hello World"
subTitle="Sorry, you are not authorized to access this page."
extra={<Button type="primary">Back Home</Button>}
/>
</div>
</ProLayout>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { CrownOutlined, SmileOutlined, UserOutlined } from '@ant-design/icons';
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import { Button, Input, Result, Tag } from 'antd';
import { useState } from 'react';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const defaultProps = {
routes: [
{
path: '/welcome',
name: '欢迎',
icon: <CrownOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page2',
name: '二级页面',
icon: <UserOutlined />,
component: './Welcome',
},
{
path: '/admin/sub-page3',
name: '三级页面',
icon: <SmileOutlined />,
component: './Welcome',
},
],
};
const Demo = () => {
const [pathname, setPathname] = useState('/welcome');
return (
<>
<ProLayout
route={defaultProps}
location={{
pathname,
}}
fixSiderbar
headerRender={false}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
avatarProps={{
icon: <UserOutlined />,
}}
>
<PageContainer
onBack={() => null}
tags={<Tag color="blue">状态一</Tag>}
header={{
style: {
padding: '8px 16px',
backgroundColor: '#fff',
position: 'fixed',
top: 0,
width: '100%',
left: 0,
zIndex: 999,
boxShadow: '0 2px 8px #f0f1f2',
},
}}
style={{
paddingBlockStart: 48,
}}
extra={[
<Input.Search
key="search"
style={{
width: 240,
}}
/>,
<Button key="3">操作一</Button>,
<Button key="2" type="primary">
操作一
</Button>,
]}
>
<div
style={{
height: '120vh',
minHeight: 600,
}}
>
<Result
status="404"
style={{
height: '100%',
background: '#fff',
}}
title="Hello World"
subTitle="Sorry, you are not authorized to access this page."
extra={<Button type="primary">Back Home</Button>}
/>
</div>
</PageContainer>
</ProLayout>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Cross-site navigation
For the default card layout, each item should have
descset to a truthy value. For grouped layout, each item should includechildrenwith length greater than 0.
import type { AppItemProps, ProSettings } from '@ant-design/pro-components';
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components';
import { Modal } from 'antd';
import defaultProps from './_defaultProps';
const AppGroupList: any = [
{
title: 'UI 设计语言',
desc: '杭州市较知名的 UI 设计语言',
children: [
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
title: 'Ant Design',
desc: '杭州市较知名的 UI 设计语言',
url: 'https://ant.design',
},
{
icon: 'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
title: 'Pro Components',
desc: '专业级 UI 组件库',
url: 'https://procomponents.ant.design/',
},
],
},
{
title: 'UI 设计语言 2组111',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
desc: '专业级 UI 组件库',
url: 'https://procomponents.ant.design/',
children: [
{
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
title: 'AntV',
desc: '蚂蚁集团全新一代数据可视化解决方案',
url: 'https://antv.vision/',
target: '_blank',
},
{
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
title: 'AntV',
desc: '蚂蚁集团全新一代数据可视化解决方案',
url: 'https://antv.vision/',
target: '_blank',
},
],
},
{
title: '待分组',
desc: '专业级 UI 组件库',
children: [
{
title: '工具',
desc: '知识创作与分享工具',
icon: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg',
url: 'https://www.yuque.com/',
},
{
title: '前端应用框架',
desc: '插件化的企业级前端应用框架。',
icon: 'https://img.alicdn.com/tfs/TB1zomHwxv1gK0jSZFFXXb0sXXa-200-200.png',
url: 'https://umijs.org/zh-CN/docs',
},
{
icon: 'https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png',
title: 'qiankun',
desc: '可能是你见过最完善的微前端解决方案🧐',
url: 'https://qiankun.umijs.org/',
},
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/LFooOLwmxGLsltmUjTAP.svg',
title: 'Kitchen ',
desc: 'Sketch 工具集',
url: 'https://kitchen.alipay.com/',
},
{
icon: 'https://gw.alipayobjects.com/zos/bmw-prod/d3e3eb39-1cd7-4aa5-827c-877deced6b7e/lalxt4g3_w256_h256.png',
title: 'dumi',
desc: '为组件开发场景而生的文档工具',
url: 'https://d.umijs.org/zh-CN',
},
],
},
];
const Demo = () => {
const settings: ProSettings = {
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
};
const itemClick = (
item: AppItemProps,
popoverRef?: React.RefObject<HTMLSpanElement>,
) => {
// 点击后关闭 Popover
popoverRef?.current?.click?.();
Modal?.confirm({
width: 600,
title: '点击项 详细数据',
content: (
<pre style={{ overflow: 'auto' }}>
{JSON.stringify(typeof item === 'object' ? item : {}, null, 2)}
</pre>
),
okText: '前往',
onOk: () => window.open(item?.url),
});
};
return (
<div id="test-pro-layout" style={{ height: '100vh' }}>
<ProConfigProvider hashed={false}>
<ProLayout
{...defaultProps}
appList={AppGroupList}
itemClick={itemClick}
location={{ pathname: '/list/sub-page/sub-sub-page1' }}
siderMenuType="group"
menu={{ collapsedShowGroupTitle: true }}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
{...settings}
/>
</ProConfigProvider>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Theme & Style Customization
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<ProLayout
splitMenus
token={{
colorBgAppListIconHover: 'rgba(0,0,0,0.06)',
colorTextAppListIconHover: 'rgba(255,255,255,0.95)',
colorTextAppListIcon: 'rgba(255,255,255,0.85)',
sider: {
colorBgCollapsedButton: '#fff',
colorTextCollapsedButtonHover: 'rgba(0,0,0,0.65)',
colorTextCollapsedButton: 'rgba(0,0,0,0.45)',
colorMenuBackground: '#004FD9',
colorBgMenuItemCollapsedElevated: 'rgba(0,0,0,0.85)',
colorMenuItemDivider: 'rgba(255,255,255,0.15)',
colorBgMenuItemHover: 'rgba(0,0,0,0.06)',
colorBgMenuItemSelected: 'rgba(0,0,0,0.15)',
colorTextMenuSelected: '#fff',
colorTextMenuItemHover: 'rgba(255,255,255,0.75)',
colorTextMenu: 'rgba(255,255,255,0.75)',
colorTextMenuSecondary: 'rgba(255,255,255,0.65)',
colorTextMenuTitle: 'rgba(255,255,255,0.95)',
colorTextMenuActive: 'rgba(255,255,255,0.95)',
colorTextSubMenuSelected: '#fff',
},
header: {
colorBgHeader: '#004FD9',
colorBgRightActionsItemHover: 'rgba(0,0,0,0.06)',
colorTextRightActionsItem: 'rgba(255,255,255,0.65)',
colorHeaderTitle: '#fff',
colorBgMenuItemHover: 'rgba(0,0,0,0.06)',
colorBgMenuItemSelected: 'rgba(0,0,0,0.15)',
colorTextMenuSelected: '#fff',
colorTextMenu: 'rgba(255,255,255,0.75)',
colorTextMenuSecondary: 'rgba(255,255,255,0.65)',
colorTextMenuActive: 'rgba(255,255,255,0.95)',
},
}}
{...defaultProps}
location={{
pathname,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<p
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
Power by Ant Design
</p>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
layout="top"
>
<PageContainer
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import {
PageContainer,
ProCard,
ProConfigProvider,
ProLayout,
} from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<ProConfigProvider dark={true}>
<ProLayout
{...defaultProps}
splitMenus
location={{
pathname,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<p
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
Power by Ant Design
</p>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
>
<PageContainer
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</ProConfigProvider>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const settings: Partial<ProSettings> | undefined = {
fixSiderbar: true,
layout: 'top',
splitMenus: true,
};
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: <div>书琰</div>,
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
}}
prefix={<SearchOutlined />}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProCard,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [settings, setSettings] = useState<Partial<ProSettings> | undefined>({
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
});
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
token={{
header: {
colorBgHeader: '#292f33',
colorHeaderTitle: '#fff',
colorTextMenu: '#dfdfdf',
colorTextMenuSecondary: '#dfdfdf',
colorTextMenuSelected: '#fff',
colorBgMenuItemSelected: '#22272b',
colorTextRightActionsItem: '#dfdfdf',
},
sider: {
colorMenuBackground: '#fff',
colorMenuItemDivider: '#dfdfdf',
colorTextMenu: '#595959',
colorTextMenuSelected: 'rgba(42,122,251,1)',
colorBgMenuItemSelected: 'rgba(230,243,254,1)',
},
}}
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: (
<div
style={{
color: '#dfdfdf',
}}
>
书琰
</div>
),
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(57,62,67,1)',
color: '#fff',
}}
prefix={
<SearchOutlined
style={{
color: '#dfdfdf',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
{...settings}
>
<PageContainer
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
<SettingDrawer
pathname={pathname}
enableDarkTheme
getContainer={() => document.getElementById('test-pro-layout')}
settings={settings}
onSettingChange={(changeSetting) => {
setSettings(changeSetting);
}}
disableUrlParams={false}
/>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
QuestionCircleFilled,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProCard,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const [settings, setSettings] = useState<Partial<ProSettings> | undefined>({
layout: 'side',
});
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
siderWidth={256}
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '100vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
<SettingDrawer
pathname={pathname}
enableDarkTheme
getContainer={() => document.getElementById('test-pro-layout')}
settings={settings}
onSettingChange={(changeSetting) => {
setSettings(changeSetting);
}}
disableUrlParams={false}
/>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const settings: Partial<ProSettings> | undefined = {
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
};
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
token={{
bgLayout: '#fff',
header: {
colorBgHeader: '#fff',
},
sider: {
colorMenuBackground: '#fff',
},
pageContainer: {
colorBgPageContainer: '#fff',
},
}}
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
menu={{
type: 'group',
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: <div>书琰</div>,
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
}}
prefix={<SearchOutlined />}
placeholder="搜索方 案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Extra demos
Examples that were previously not wired in the docs: ConfigProvider prefix, breadcrumbRender, menuRender, grouped appList, nested top layout, etc.
import { LikeOutlined, UserOutlined } from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import {
Button,
ConfigProvider,
Descriptions,
Result,
Space,
Statistic,
} from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const content = (
<Descriptions size="small" column={2}>
<Descriptions.Item label="创建人">书琰</Descriptions.Item>
<Descriptions.Item label="联系方式">
<span>421421</span>
</Descriptions.Item>
<Descriptions.Item label="创建时间">2017-01-10</Descriptions.Item>
<Descriptions.Item label="更新时间">2017-10-10</Descriptions.Item>
<Descriptions.Item label="备注">
中国浙江省杭州市西湖区古翠路
</Descriptions.Item>
</Descriptions>
);
const Demo = () => {
const [settings, setSettings] = useState<Partial<ProSettings> | undefined>(
undefined,
);
const [pathname, setPathname] = useState('/welcome');
return (
<ConfigProvider prefixCls="qixian">
<ProLayout
{...defaultProps}
style={{
maxHeight: '100vh',
}}
location={{
pathname,
}}
menuFooterRender={(props) => {
return (
<a
style={{
lineHeight: '48rpx',
display: 'flex',
height: 48,
color: 'rgba(255, 255, 255, 0.65)',
alignItems: 'center',
}}
href="https://preview.pro.ant.design/dashboard/analysis"
target="_blank"
rel="noreferrer"
>
<img
alt="pro-logo"
src="https://procomponents.ant.design/favicon.ico"
style={{
width: 16,
height: 16,
margin: '0 16px',
marginInlineEnd: 10,
}}
/>
{!props?.collapsed && 'Preview Pro'}
</a>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<div
role="button"
tabIndex={0}
onClick={() => {
setPathname(item.path || '/welcome');
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setPathname(item.path || '/welcome');
}
}}
>
{dom}
</div>
)}
avatarProps={{
icon: <UserOutlined />,
}}
{...settings}
>
<PageContainer
content={content}
tabList={[
{
tab: '基本信息',
key: 'base',
},
{
tab: '详细信息',
key: 'info',
},
]}
extraContent={
<Space size={24}>
<Statistic
title="Feedback"
value={1128}
prefix={<LikeOutlined />}
/>
<Statistic title="Unmerged" value={93} suffix="/ 100" />
</Space>
}
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<div
style={{
height: '120vh',
minHeight: 600,
}}
>
<Result
status="404"
style={{
height: '100%',
background: '#fff',
}}
title="Hello World"
subTitle="Sorry, you are not authorized to access this page."
extra={<Button type="primary">Back Home</Button>}
/>
</div>
</PageContainer>
</ProLayout>
<SettingDrawer
getContainer={() => document.getElementById('test-pro-layout')}
settings={settings}
onSettingChange={(changeSetting) => setSettings(changeSetting)}
/>
</ConfigProvider>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import complexMenu from './complex-menu';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/data_hui/data_hui2',
}}
route={{
routes: complexMenu,
}}
menu={{ type: 'group' }}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import defaultProps from './_defaultProps';
const Demo = () => (
<ProLayout
{...defaultProps}
style={{
height: '100vh',
}}
location={{
pathname: '/welcome',
}}
menuRender={(props, dom) => (
<div
style={{
background: '#fff',
boxShadow: '2px 0 6px rgba(0, 21, 41, 0.35)',
overflow: 'hidden',
height: '100%',
width: props.collapsed ? 0 : props.siderWidth || 256,
}}
>
{dom}
</div>
)}
>
<PageContainer content="欢迎使用">Hello World</PageContainer>
</ProLayout>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import complexMenu from './complex-menu';
const Demo = () => (
<div
style={{
height: '100vh',
overflow: 'auto',
}}
>
<ProLayout
location={{
pathname: '/articles/new',
}}
route={{
routes: complexMenu,
}}
layout="top"
>
<ProLayout
location={{
pathname: '/home/overview',
}}
route={{
routes: complexMenu,
}}
menuHeaderRender={false}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { PageContainer, ProLayout } from '@ant-design/pro-components';
const Demo = () => (
<div
style={{
height: '100vh',
}}
>
<ProLayout
location={{
pathname: '/admin/process/edit/123',
}}
breadcrumbRender={(routes) => [
{
path: '/',
title: '主页',
},
...(routes || []),
]}
menuDataRender={() => [
{
path: '/welcome',
name: '欢迎',
},
{
path: '/admin',
name: '管理',
children: [
{
name: '申请单列表',
path: '/admin/process',
},
{
name: '申请单详情',
path: '/admin/process/detail/:id',
hideInMenu: true,
},
{
name: '编辑申请单',
path: '/admin/process/edit/:id',
hideInMenu: true,
},
{
name: '添加申请单',
path: '/admin/process/add',
hideInMenu: true,
},
],
},
]}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import type { ProSettings } from '@ant-design/pro-components';
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components';
import defaultProps from './_defaultProps';
const AppGroupList: any = [
{
title: 'UI 设计语言',
children: [
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
title: '应用内跳转',
url: '/components/page-container',
},
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
title: 'Ant Design',
url: 'https://ant.design',
},
{
icon: () => (
<img src="https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg" alt="" />
),
title: 'Pro Components',
url: 'https://procomponents.ant.design/',
},
],
},
{
title: 'UI 设计语言 2组111',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
url: 'https://procomponents.ant.design/',
children: [
{
icon: 'W',
title: 'AntV',
url: 'https://antv.vision/',
target: '_blank',
},
{
title: 'AntV',
url: 'https://antv.vision/',
target: '_blank',
},
],
},
{
title: '待分组',
children: [
{
title: '工具',
icon: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg',
url: 'https://www.yuque.com/',
},
{
title: '前端应用框架',
icon: 'https://img.alicdn.com/tfs/TB1zomHwxv1gK0jSZFFXXb0sXXa-200-200.png',
url: 'https://umijs.org/zh-CN/docs',
},
{
icon: 'https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png',
title: 'qiankun',
url: 'https://qiankun.umijs.org/',
},
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/LFooOLwmxGLsltmUjTAP.svg',
title: 'Kitchen ',
url: 'https://kitchen.alipay.com/',
},
{
icon: 'https://gw.alipayobjects.com/zos/bmw-prod/d3e3eb39-1cd7-4aa5-827c-877deced6b7e/lalxt4g3_w256_h256.png',
title: 'dumi',
url: 'https://d.umijs.org/zh-CN',
},
],
},
];
const Demo = () => {
const settings: ProSettings = {
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
};
return (
<div id="test-pro-layout" style={{ height: '100vh' }}>
<ProConfigProvider hashed={false}>
<ProLayout
{...defaultProps}
appList={AppGroupList}
location={{ pathname: '/list/sub-page/sub-sub-page1' }}
siderMenuType="group"
menu={{ collapsedShowGroupTitle: true }}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
{...settings}
/>
</ProConfigProvider>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Others
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import type { ProLayoutProps } from '@ant-design/pro-components';
import {
PageContainer,
ProFormRadio,
ProLayout,
} from '@ant-design/pro-components';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const [pathname, setPathname] = useState('/welcome');
const [collapsed, setCollapsed] = useState(true);
const [position, setPosition] = useState<'header' | 'menu'>('header');
const children = (
<PageContainer>
<div
style={{
height: '120vh',
minHeight: 600,
}}
>
<ProFormRadio.Group
label="按钮的位置"
options={['header', 'menu'].map((value) => ({
label: value,
value,
}))}
fieldProps={{
value: position,
onChange: (e) => setPosition(e.target.value),
}}
/>
</div>
</PageContainer>
);
const props: ProLayoutProps = {
...defaultProps,
location: {
pathname,
},
collapsed,
fixSiderbar: true,
collapsedButtonRender: false,
menuItemRender: (item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
),
};
if (position === 'menu') {
return (
<ProLayout
{...props}
layout="mix"
onCollapse={setCollapsed}
postMenuData={(menuData) => {
return [
{
icon: collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />,
name: ' ',
onTitleClick: () => setCollapsed(!collapsed),
},
...(menuData || []),
];
}}
>
{children}
</ProLayout>
);
}
return (
<ProLayout
{...props}
layout="mix"
onCollapse={setCollapsed}
headerContentRender={() => {
return (
<div
onClick={() => setCollapsed(!collapsed)}
style={{
cursor: 'pointer',
fontSize: '16px',
}}
>
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</div>
);
}}
>
{children}
</ProLayout>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
QuestionCircleFilled,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProCard,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import { Avatar, Image, Space } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
const Demo = () => {
const [settings, setSettings] = useState<Partial<ProSettings> | undefined>({
layout: 'side',
});
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
collapsed={false}
menu={{
type: 'group',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
<div
key={1}
style={{
height: '200px',
}}
>
<Image
width={'100%'}
preview={false}
height={132}
src="https://gw.alipayobjects.com/zos/bmw-prod/d283f09a-64d6-4d59-bfc7-37b49ea0da2b.svg"
/>
<Space
align="center"
size="middle"
style={{
width: '100%',
marginBlockStart: '32px',
}}
>
<Avatar
src="https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg"
size="small"
/>
<div
style={{
fontSize: '14px',
marginInlineEnd: '32px',
}}
>
书琰
</div>
<InfoCircleFilled key="InfoCircleFilled" />
<QuestionCircleFilled key="QuestionCircleFilled" />
<GithubFilled key="GithubFilled" />
</Space>
</div>,
];
}}
menuRender={(_, defaultDom) => defaultDom}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer>
<ProCard
style={{
height: '100vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
<SettingDrawer
pathname={pathname}
enableDarkTheme
getContainer={() => document.getElementById('test-pro-layout')}
settings={settings}
onSettingChange={(changeSetting) => {
setSettings(changeSetting);
}}
disableUrlParams={false}
/>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
FacebookOutlined,
StarFilled,
TwitterOutlined,
} from '@ant-design/icons';
import { PageContainer, ProLayout } from '@ant-design/pro-components';
/**
* 菜单 `icon` 支持 React 节点(含 @ant-design/icons)或图片/SVG URL 字符串。
*
* icon 选型说明:
* - `StarFilled`:通用动作类图标,有官方 Filled 版,统一用面性
* - `FacebookOutlined` / `TwitterOutlined`:品牌 logo 官方只有单一 Outlined 形态,保留
*/
const Demo = () => (
<div style={{ height: '100vh' }}>
<ProLayout
location={{
pathname: '/favorites',
}}
route={{
routes: [
{
path: '/favorites',
name: '我的收藏',
icon: <StarFilled />,
},
{
path: '/social/facebook',
name: 'Facebook',
icon: <FacebookOutlined />,
},
{
path: '/social/twitter',
name: 'Twitter',
icon: <TwitterOutlined />,
},
{
path: '/svg-asset',
name: 'SVG 资源',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/UCSef1BATc/nice.svg',
},
],
}}
>
<PageContainer content="欢迎使用">
<div>Hello World</div>
</PageContainer>
</ProLayout>
</div>
);
export default () => (
<div>
<Demo />
</div>
);
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Descriptions } from 'antd';
import defaultProps from './_defaultProps';
const content = (
<Descriptions size="small" column={2}>
<Descriptions.Item label="创建人">书琰</Descriptions.Item>
<Descriptions.Item label="联系方式">
<a>421421</a>
</Descriptions.Item>
<Descriptions.Item label="创建时间">2017-01-10</Descriptions.Item>
<Descriptions.Item label="更新时间">2017-10-10</Descriptions.Item>
<Descriptions.Item label="备注">
中国浙江省杭州市西湖区古翠路
</Descriptions.Item>
</Descriptions>
);
const Demo = () => {
return (
<div
style={{
height: '100vh',
}}
>
<ProLayout
{...defaultProps}
location={{
pathname: '/welcome',
}}
>
<PageContainer fixedHeader content={content}>
<ProCard
direction="column"
ghost
gutter={[0, 16]}
style={{
height: '200vh',
}}
>
<ProCard style={{ height: 200 }} />
<ProCard gutter={16} ghost style={{ height: 200 }}>
<ProCard colSpan={16} />
<ProCard colSpan={8} />
</ProCard>
</ProCard>
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Error boundaries
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import { Button, Result } from 'antd';
import type { ErrorInfo } from 'react';
import React, { useState } from 'react';
class CustomBoundary extends React.Component<
Record<string, any>,
{ hasError: boolean; errorInfo: string }
> {
state = { hasError: false, errorInfo: '' };
static getDerivedStateFromError(error: Error) {
return { hasError: true, errorInfo: error.message };
}
componentDidCatch(error: any, errorInfo: ErrorInfo) {
// You can also log the error to an error reporting service
console.error(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<Result
icon={
<img
width={256}
src="https://gw.alipayobjects.com/zos/antfincdn/zIgkN%26mpMZ/shibaizhuangtaizuo.png"
/>
}
style={{
height: '100%',
background: '#fff',
}}
title="错误处理"
extra={
<>
<div
style={{
maxWidth: 620,
textAlign: 'start',
backgroundColor: 'rgba(255,229,100,0.3)',
borderInlineStartColor: '#ffe564',
borderInlineStartWidth: '9px',
borderInlineStartStyle: 'solid',
padding: '20px 45px 20px 26px',
margin: 'auto',
marginBlockEnd: '30px',
marginBlockStart: '20px',
}}
>
<p>注意</p>
<p>
错误边界<strong>无法</strong>捕获以下场景中产生的错误:
</p>
<ul
style={{
listStyle: 'none',
}}
>
<li>
事件处理(
<a href="https://zh-hans.reactjs.org/docs/error-boundaries.html#how-about-event-handlers#how-about-event-handlers">
了解更多
</a>
)
</li>
<li>
异步代码(例如 <code>setTimeout</code> 或{' '}
<code>requestAnimationFrame</code> 回调函数)
</li>
<li>服务端渲染</li>
<li>它自身抛出来的错误(并非它的子组件)</li>
</ul>
</div>
<Button
danger
type="primary"
onClick={() => {
window.location.reload();
}}
>
刷新页面
</Button>
</>
}
/>
);
}
return this.props.children;
}
}
const ErrorTriggerTestPage = () => {
// default to throw error for snapshot test
const [error, setError] = useState<boolean>(true);
if (error) throw new Error('渲染发生了错误');
return (
<Button
danger
type="primary"
onClick={() => {
setError(true);
}}
>
触发错误
</Button>
);
};
const Demo = () => {
const [pathname, setPathname] = useState('/default');
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
}}
>
<ProLayout
route={{
path: '/',
routes: [
{
path: '/default',
name: '默认错误',
},
{
path: '/custom',
name: '自定义错误',
},
],
}}
location={{
pathname,
}}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
ErrorBoundary={pathname === '/custom' ? CustomBoundary : undefined}
>
<PageContainer>
<ErrorTriggerTestPage />
</PageContainer>
</ProLayout>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
GithubFilled,
InfoCircleFilled,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import { PageContainer, ProCard, ProLayout } from '@ant-design/pro-components';
import { Button, Input } from 'antd';
import { useState } from 'react';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
return (
<ProLayout
splitMenus
layout="mix"
token={{
bgLayout: 'rgba(160, 217, 17, 0.1)',
colorTextAppListIcon: 'rgba(255, 255, 255, 1)',
colorTextAppListIconHover: 'rgba(245, 34, 45, 1)',
sider: {
colorBgMenuItemHover: 'rgba(24, 144, 255, 1)',
colorMenuBackground: 'rgba(24, 144, 255, 0.1)',
colorMenuItemDivider: 'rgba(250, 219, 20, 1)',
colorTextMenu: 'rgba(250, 219, 20, 1)',
colorTextMenuSelected: 'rgba(250, 84, 28, 1)',
colorTextMenuItemHover: 'rgba(255, 255, 255, 1)',
colorBgMenuItemSelected: 'rgb(82, 196, 26)',
colorBgCollapsedButton: 'rgba(250, 84, 28, 1)',
colorTextCollapsedButton: 'rgba(24, 144, 255, 1)',
colorTextCollapsedButtonHover: 'rgba(143, 35, 35, 1)',
},
header: {
colorBgMenuItemSelected: 'rgba(82, 196, 26, 1)',
colorTextMenuSelected: 'rgba(114, 46, 209, 1)',
colorBgHeader: 'rgba(250, 173, 20, 1)',
colorHeaderTitle: 'rgba(47, 84, 235, 1)',
colorTextMenuActive: 'rgba(255, 255, 255, 1)',
colorTextMenu: 'rgba(250, 84, 28, 1)',
colorBgMenuItemHover: 'rgba(24, 144, 255, 1)',
},
pageContainer: {
colorBgPageContainer: 'rgba(250, 84, 28, 1)',
},
}}
{...defaultProps}
location={{
pathname,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '书琰',
}}
actionsRender={(props) => {
if (props.isMobile) return [];
return [
props.layout !== 'side' ? (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: 'rgba(0,0,0,0.03)',
}}
prefix={
<SearchOutlined
style={{
color: 'rgba(0, 0, 0, 0.15)',
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: 'var(--ant-primary-color)',
fontSize: 24,
}}
/>
</div>
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<p
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
Power by Ant Design
</p>
);
}}
onMenuHeaderClick={demoOnMenuHeaderClick}
menuItemRender={(item, dom) => (
<a
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</a>
)}
>
<PageContainer
extra={[
<Button key="3">操作</Button>,
<Button key="2">操作</Button>,
<Button key="1" type="primary">
主操作
</Button>,
]}
footer={[
<Button key="3">重置</Button>,
<Button key="2" type="primary">
提交
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
</ProLayout>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
PageContainer,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import defaultProps from './_defaultProps';
import { demoOnMenuHeaderClick } from './_demoHandlers';
const Demo = () => {
return (
<>
<ProLayout {...defaultProps} layout="mix" splitMenus pure>
<PageContainer>splitMenus + pure</PageContainer>
</ProLayout>
<ProLayout {...defaultProps} layout="mix" splitMenus>
<PageContainer>splitMenus</PageContainer>
</ProLayout>
<ProLayout
{...defaultProps}
breadcrumbRender={false}
layout="mix"
splitMenus
headerRender={false}
onMenuHeaderClick={demoOnMenuHeaderClick}
formatMessage={({ id }) => id}
style={{
height: '100vh',
}}
/>
<ProLayout
{...defaultProps}
layout="mix"
menuExtraRender={() => 'dom'}
menuHeaderRender={() => <div />}
splitMenus
location={{
pathname: '/welcome',
}}
style={{
height: '100vh',
}}
/>
<ProLayout
{...defaultProps}
layout="top"
collapsed
contentWidth="Fixed"
menuExtraRender={false}
menuHeaderRender={false}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
}}
location={{
pathname: '/welcome',
}}
style={{
height: '100vh',
}}
/>
<ProLayout
{...defaultProps}
layout="mix"
menuHeaderRender={() => null}
splitMenus
fixSiderbar
location={{
pathname: '/welcome',
}}
contentWidth="Fixed"
openKeys={false}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
}}
style={{
height: '100vh',
}}
>
<PageContainer>splitMenus + fixSiderbar</PageContainer>
</ProLayout>
<ProLayout
{...defaultProps}
layout="mix"
menuHeaderRender={() => null}
location={{
pathname: '/welcome',
}}
openKeys={false}
actionsRender={() => [
<a key="docs" href="https://procomponents.ant.design/">
文档
</a>,
]}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
}}
style={{
height: '100vh',
}}
>
<PageContainer>顶部操作区示例</PageContainer>
</ProLayout>
<ProLayout
{...defaultProps}
layout="mix"
menuHeaderRender={() => null}
splitMenus={false}
fixSiderbar
location={{
pathname: '/welcome',
}}
contentWidth="Fixed"
openKeys={false}
style={{
height: '100vh',
}}
>
<PageContainer>splitMenus 关闭时的侧栏与内容</PageContainer>
</ProLayout>
<SettingDrawer collapse />
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);