Browse Source
* use antd page-header * remove page-header * up ant version * up antd version * up antd verison * use umi-request (#3883) * use umi-request * use data repalce to body * 401 不需要提示请求错误。 * use netlify functions (#3882) * use netlify functions * rm .gitignore key * fix build error * use new folder * change config * add redirects * add redirects * add router * remobe "/" * remove code * add tag * remove babel loader * rm firebase config * remove babel * bugfix: fix#3891 ,dll config error * add function build * fix error script * feat: win add test script (#3845) * feat: win add test script * bugfix: fix test error * use new config * style: change title size * style: change title sizepull/3911/head
committed by
GitHub
15 changed files with 312 additions and 844 deletions
@ -1,6 +0,0 @@ |
|||
import React from 'react'; |
|||
import { PageHeaderProps } from './index'; |
|||
|
|||
export default class BreadcrumbView extends React.Component<PageHeaderProps, any> {} |
|||
|
|||
export function getBreadcrumb(breadcrumbNameMap: object, url: string): object; |
|||
@ -1,178 +0,0 @@ |
|||
import React, { PureComponent, createElement } from 'react'; |
|||
import pathToRegexp from 'path-to-regexp'; |
|||
import { Breadcrumb } from 'antd'; |
|||
import styles from './index.less'; |
|||
import { urlToList } from '../_utils/pathTools'; |
|||
|
|||
export const getBreadcrumb = (breadcrumbNameMap, url) => { |
|||
let breadcrumb = breadcrumbNameMap[url]; |
|||
if (!breadcrumb) { |
|||
Object.keys(breadcrumbNameMap).forEach(item => { |
|||
if (pathToRegexp(item).test(url)) { |
|||
breadcrumb = breadcrumbNameMap[item]; |
|||
} |
|||
}); |
|||
} |
|||
return breadcrumb || {}; |
|||
}; |
|||
|
|||
export default class BreadcrumbView extends PureComponent { |
|||
state = { |
|||
breadcrumb: null, |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
this.getBreadcrumbDom(); |
|||
} |
|||
|
|||
componentDidUpdate(preProps) { |
|||
const { location } = this.props; |
|||
if (!location || !preProps.location) { |
|||
return; |
|||
} |
|||
const prePathname = preProps.location.pathname; |
|||
if (prePathname !== location.pathname) { |
|||
this.getBreadcrumbDom(); |
|||
} |
|||
} |
|||
|
|||
getBreadcrumbDom = () => { |
|||
const breadcrumb = this.conversionBreadcrumbList(); |
|||
this.setState({ |
|||
breadcrumb, |
|||
}); |
|||
}; |
|||
|
|||
getBreadcrumbProps = () => { |
|||
const { routes, params, location, breadcrumbNameMap } = this.props; |
|||
return { |
|||
routes, |
|||
params, |
|||
routerLocation: location, |
|||
breadcrumbNameMap, |
|||
}; |
|||
}; |
|||
|
|||
// Generated according to props
|
|||
conversionFromProps = () => { |
|||
const { breadcrumbList, breadcrumbSeparator, itemRender, linkElement = 'a' } = this.props; |
|||
return ( |
|||
<Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}> |
|||
{breadcrumbList.map(item => { |
|||
const title = itemRender ? itemRender(item) : item.title; |
|||
return ( |
|||
<Breadcrumb.Item key={item.title}> |
|||
{item.href |
|||
? createElement( |
|||
linkElement, |
|||
{ |
|||
[linkElement === 'a' ? 'href' : 'to']: item.href, |
|||
}, |
|||
title |
|||
) |
|||
: title} |
|||
</Breadcrumb.Item> |
|||
); |
|||
})} |
|||
</Breadcrumb> |
|||
); |
|||
}; |
|||
|
|||
conversionFromLocation = (routerLocation, breadcrumbNameMap) => { |
|||
const { breadcrumbSeparator, home, itemRender, linkElement = 'a' } = this.props; |
|||
// Convert the url to an array
|
|||
const pathSnippets = urlToList(routerLocation.pathname); |
|||
// Loop data mosaic routing
|
|||
const extraBreadcrumbItems = pathSnippets.map((url, index) => { |
|||
const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url); |
|||
if (currentBreadcrumb.inherited) { |
|||
return null; |
|||
} |
|||
const isLinkable = index !== pathSnippets.length - 1 && currentBreadcrumb.component; |
|||
const name = itemRender ? itemRender(currentBreadcrumb) : currentBreadcrumb.name; |
|||
return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? ( |
|||
<Breadcrumb.Item key={url}> |
|||
{createElement( |
|||
isLinkable ? linkElement : 'span', |
|||
{ [linkElement === 'a' ? 'href' : 'to']: url }, |
|||
name |
|||
)} |
|||
</Breadcrumb.Item> |
|||
) : null; |
|||
}); |
|||
// Add home breadcrumbs to your head if defined
|
|||
if (home) { |
|||
extraBreadcrumbItems.unshift( |
|||
<Breadcrumb.Item key="home"> |
|||
{createElement( |
|||
linkElement, |
|||
{ |
|||
[linkElement === 'a' ? 'href' : 'to']: '/', |
|||
}, |
|||
home |
|||
)} |
|||
</Breadcrumb.Item> |
|||
); |
|||
} |
|||
return ( |
|||
<Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}> |
|||
{extraBreadcrumbItems} |
|||
</Breadcrumb> |
|||
); |
|||
}; |
|||
|
|||
/** |
|||
* 将参数转化为面包屑 |
|||
* Convert parameters into breadcrumbs |
|||
*/ |
|||
conversionBreadcrumbList = () => { |
|||
const { breadcrumbList, breadcrumbSeparator } = this.props; |
|||
const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps(); |
|||
if (breadcrumbList && breadcrumbList.length) { |
|||
return this.conversionFromProps(); |
|||
} |
|||
// 如果传入 routes 和 params 属性
|
|||
// If pass routes and params attributes
|
|||
if (routes && params) { |
|||
return ( |
|||
<Breadcrumb |
|||
className={styles.breadcrumb} |
|||
routes={routes.filter(route => route.breadcrumbName)} |
|||
params={params} |
|||
itemRender={this.itemRender} |
|||
separator={breadcrumbSeparator} |
|||
/> |
|||
); |
|||
} |
|||
// 根据 location 生成 面包屑
|
|||
// Generate breadcrumbs based on location
|
|||
if (routerLocation && routerLocation.pathname) { |
|||
return this.conversionFromLocation(routerLocation, breadcrumbNameMap); |
|||
} |
|||
return null; |
|||
}; |
|||
|
|||
// 渲染Breadcrumb 子节点
|
|||
// Render the Breadcrumb child node
|
|||
itemRender = (route, params, routes, paths) => { |
|||
const { linkElement = 'a' } = this.props; |
|||
const last = routes.indexOf(route) === routes.length - 1; |
|||
return last || !route.component ? ( |
|||
<span>{route.breadcrumbName}</span> |
|||
) : ( |
|||
createElement( |
|||
linkElement, |
|||
{ |
|||
href: paths.join('/') || '/', |
|||
to: paths.join('/') || '/', |
|||
}, |
|||
route.breadcrumbName |
|||
) |
|||
); |
|||
}; |
|||
|
|||
render() { |
|||
const { breadcrumb } = this.state; |
|||
return breadcrumb; |
|||
} |
|||
} |
|||
@ -1,75 +0,0 @@ |
|||
--- |
|||
order: 2 |
|||
title: With Image |
|||
--- |
|||
|
|||
带图片的页头。 |
|||
|
|||
````jsx |
|||
import PageHeader from 'ant-design-pro/lib/PageHeader'; |
|||
|
|||
const content = ( |
|||
<div> |
|||
<p>段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。</p> |
|||
<div className="link"> |
|||
<a> |
|||
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" /> 快速开始 |
|||
</a> |
|||
<a> |
|||
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg" /> 产品简介 |
|||
</a> |
|||
<a> |
|||
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" /> 产品文档 |
|||
</a> |
|||
</div> |
|||
</div> |
|||
); |
|||
|
|||
const extra = ( |
|||
<div className="imgContainer"> |
|||
<img style={{ width: '100%' }} alt="" src="https://gw.alipayobjects.com/zos/rmsportal/RzwpdLnhmvDJToTdfDPe.png" /> |
|||
</div> |
|||
); |
|||
|
|||
const breadcrumbList = [{ |
|||
title: '一级菜单', |
|||
href: '/', |
|||
}, { |
|||
title: '二级菜单', |
|||
href: '/', |
|||
}, { |
|||
title: '三级菜单', |
|||
}]; |
|||
|
|||
ReactDOM.render( |
|||
<div> |
|||
<PageHeader |
|||
title="这是一个标题" |
|||
content={content} |
|||
extraContent={extra} |
|||
breadcrumbList={breadcrumbList} |
|||
/> |
|||
</div> |
|||
, mountNode); |
|||
```` |
|||
|
|||
<style> |
|||
#scaffold-src-components-PageHeader-demo-image .code-box-demo { |
|||
background: #f2f4f5; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-image .imgContainer { |
|||
margin-top: -60px; |
|||
text-align: center; |
|||
width: 195px; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-image .link { |
|||
margin-top: 16px; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-image .link a { |
|||
margin-right: 32px; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-image .link img { |
|||
vertical-align: middle; |
|||
margin-right: 8px; |
|||
} |
|||
</style> |
|||
@ -1,32 +0,0 @@ |
|||
--- |
|||
order: 3 |
|||
title: Simple |
|||
--- |
|||
|
|||
简单的页头。 |
|||
|
|||
````jsx |
|||
import PageHeader from 'ant-design-pro/lib/PageHeader'; |
|||
|
|||
const breadcrumbList = [{ |
|||
title: '一级菜单', |
|||
href: '/', |
|||
}, { |
|||
title: '二级菜单', |
|||
href: '/', |
|||
}, { |
|||
title: '三级菜单', |
|||
}]; |
|||
|
|||
ReactDOM.render( |
|||
<div> |
|||
<PageHeader title="页面标题" breadcrumbList={breadcrumbList} /> |
|||
</div> |
|||
, mountNode); |
|||
```` |
|||
|
|||
<style> |
|||
#scaffold-src-components-PageHeader-demo-simple .code-box-demo { |
|||
background: #f2f4f5; |
|||
} |
|||
</style> |
|||
@ -1,102 +0,0 @@ |
|||
--- |
|||
order: 1 |
|||
title: Standard |
|||
--- |
|||
|
|||
标准页头。 |
|||
|
|||
````jsx |
|||
import PageHeader from 'ant-design-pro/lib/PageHeader'; |
|||
import DescriptionList from 'ant-design-pro/lib/DescriptionList'; |
|||
import { Button, Menu, Dropdown, Icon, Row, Col } from 'antd'; |
|||
|
|||
const { Description } = DescriptionList; |
|||
const ButtonGroup = Button.Group; |
|||
|
|||
const description = ( |
|||
<DescriptionList size="small" col="2"> |
|||
<Description term="创建人">曲丽丽</Description> |
|||
<Description term="订购产品">XX 服务</Description> |
|||
<Description term="创建时间">2017-07-07</Description> |
|||
<Description term="关联单据"><a href="">12421</a></Description> |
|||
</DescriptionList> |
|||
); |
|||
|
|||
const menu = ( |
|||
<Menu> |
|||
<Menu.Item key="1">选项一</Menu.Item> |
|||
<Menu.Item key="2">选项二</Menu.Item> |
|||
<Menu.Item key="3">选项三</Menu.Item> |
|||
</Menu> |
|||
); |
|||
|
|||
const action = ( |
|||
<div> |
|||
<ButtonGroup> |
|||
<Button>操作</Button> |
|||
<Button>操作</Button> |
|||
<Dropdown overlay={menu} placement="bottomRight"> |
|||
<Button><Icon type="ellipsis" /></Button> |
|||
</Dropdown> |
|||
</ButtonGroup> |
|||
<Button type="primary">主操作</Button> |
|||
</div> |
|||
); |
|||
|
|||
const extra = ( |
|||
<Row> |
|||
<Col sm={24} md={12}> |
|||
<div style={{ color: 'rgba(0, 0, 0, 0.43)' }}>状态</div> |
|||
<div style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 20 }}>待审批</div> |
|||
</Col> |
|||
<Col sm={24} md={12}> |
|||
<div style={{ color: 'rgba(0, 0, 0, 0.43)' }}>订单金额</div> |
|||
<div style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 20 }}>¥ 568.08</div> |
|||
</Col> |
|||
</Row> |
|||
); |
|||
|
|||
const breadcrumbList = [{ |
|||
title: '一级菜单', |
|||
href: '/', |
|||
}, { |
|||
title: '二级菜单', |
|||
href: '/', |
|||
}, { |
|||
title: '三级菜单', |
|||
}]; |
|||
|
|||
const tabList = [{ |
|||
key: 'detail', |
|||
tab: '详情', |
|||
}, { |
|||
key: 'rule', |
|||
tab: '规则', |
|||
}]; |
|||
|
|||
function onTabChange(key) { |
|||
console.log(key); |
|||
} |
|||
|
|||
ReactDOM.render( |
|||
<div> |
|||
<PageHeader |
|||
title="单号:234231029431" |
|||
logo={<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png" />} |
|||
action={action} |
|||
content={description} |
|||
extraContent={extra} |
|||
breadcrumbList={breadcrumbList} |
|||
tabList={tabList} |
|||
tabActiveKey="detail" |
|||
onTabChange={onTabChange} |
|||
/> |
|||
</div> |
|||
, mountNode); |
|||
```` |
|||
|
|||
<style> |
|||
#scaffold-src-components-PageHeader-demo-standard .code-box-demo { |
|||
background: #f2f4f5; |
|||
} |
|||
</style> |
|||
@ -1,68 +0,0 @@ |
|||
--- |
|||
order: 0 |
|||
title: Structure |
|||
--- |
|||
|
|||
基本结构,具备响应式布局功能,主要断点为 768px 和 576px,拖动窗口改变大小试试看。 |
|||
|
|||
````jsx |
|||
import PageHeader from 'ant-design-pro/lib/PageHeader'; |
|||
|
|||
const breadcrumbList = [{ |
|||
title: '面包屑', |
|||
}]; |
|||
|
|||
const tabList = [{ |
|||
key: '1', |
|||
tab: '页签一', |
|||
}, { |
|||
key: '2', |
|||
tab: '页签二', |
|||
}, { |
|||
key: '3', |
|||
tab: '页签三', |
|||
}]; |
|||
|
|||
ReactDOM.render( |
|||
<div> |
|||
<PageHeader |
|||
className="tabs" |
|||
title={<div className="title">Title</div>} |
|||
logo={<div className="logo">logo</div>} |
|||
action={<div className="action">action</div>} |
|||
content={<div className="content">content</div>} |
|||
extraContent={<div className="extraContent">extraContent</div>} |
|||
breadcrumbList={breadcrumbList} |
|||
tabList={tabList} |
|||
tabActiveKey="1" |
|||
/> |
|||
</div> |
|||
, mountNode); |
|||
```` |
|||
|
|||
<style> |
|||
#scaffold-src-components-PageHeader-demo-structure .code-box-demo { |
|||
background: #f2f4f5; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-structure .logo { |
|||
background: #3ba0e9; |
|||
color: #fff; |
|||
height: 100%; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-structure .title { |
|||
background: rgba(16, 142, 233, 1); |
|||
color: #fff; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-structure .action { |
|||
background: #7dbcea; |
|||
color: #fff; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-structure .content { |
|||
background: #7dbcea; |
|||
color: #fff; |
|||
} |
|||
#scaffold-src-components-PageHeader-demo-structure .extraContent { |
|||
background: #7dbcea; |
|||
color: #fff; |
|||
} |
|||
</style> |
|||
@ -1,31 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Location } from 'history'; |
|||
|
|||
export interface PageHeaderProps { |
|||
title?: React.ReactNode | string | number; |
|||
logo?: React.ReactNode | string; |
|||
action?: React.ReactNode | string; |
|||
content?: React.ReactNode; |
|||
extraContent?: React.ReactNode; |
|||
routes?: any[]; |
|||
params?: any; |
|||
breadcrumbList?: Array<{ title: string | number; href?: string }>; |
|||
tabList?: Array<{ key: string; tab: React.ReactNode }>; |
|||
tabActiveKey?: string; |
|||
tabDefaultActiveKey?: string; |
|||
onTabChange?: (key: string) => void; |
|||
tabBarExtraContent?: React.ReactNode; |
|||
linkElement?: React.ReactNode | string; |
|||
style?: React.CSSProperties; |
|||
home?: React.ReactNode; |
|||
wide?: boolean; |
|||
hiddenBreadcrumb?: boolean; |
|||
className?: string; |
|||
loading?: boolean; |
|||
breadcrumbSeparator?: React.ReactNode; |
|||
location?: Location; |
|||
itemRender: (menuItem: any) => React.ReactNode; |
|||
breadcrumbNameMap?: any; |
|||
} |
|||
|
|||
export default class PageHeader extends React.Component<PageHeaderProps, any> {} |
|||
@ -1,82 +0,0 @@ |
|||
import React, { PureComponent } from 'react'; |
|||
import { Tabs, Skeleton } from 'antd'; |
|||
import classNames from 'classnames'; |
|||
import styles from './index.less'; |
|||
import BreadcrumbView from './breadcrumb'; |
|||
|
|||
const { TabPane } = Tabs; |
|||
export default class PageHeader extends PureComponent { |
|||
onChange = key => { |
|||
const { onTabChange } = this.props; |
|||
if (onTabChange) { |
|||
onTabChange(key); |
|||
} |
|||
}; |
|||
|
|||
render() { |
|||
const { |
|||
title = '', |
|||
logo, |
|||
action, |
|||
content, |
|||
extraContent, |
|||
tabList, |
|||
className, |
|||
tabActiveKey, |
|||
tabDefaultActiveKey, |
|||
tabBarExtraContent, |
|||
loading = false, |
|||
wide = false, |
|||
hiddenBreadcrumb = false, |
|||
} = this.props; |
|||
|
|||
const clsString = classNames(styles.pageHeader, className); |
|||
const activeKeyProps = {}; |
|||
if (tabDefaultActiveKey !== undefined) { |
|||
activeKeyProps.defaultActiveKey = tabDefaultActiveKey; |
|||
} |
|||
if (tabActiveKey !== undefined) { |
|||
activeKeyProps.activeKey = tabActiveKey; |
|||
} |
|||
return ( |
|||
<div className={clsString}> |
|||
<div className={wide ? styles.wide : ''}> |
|||
<Skeleton |
|||
loading={loading} |
|||
title={false} |
|||
active |
|||
paragraph={{ rows: 3 }} |
|||
avatar={{ size: 'large', shape: 'circle' }} |
|||
> |
|||
{hiddenBreadcrumb ? null : <BreadcrumbView {...this.props} />} |
|||
<div className={styles.detail}> |
|||
{logo && <div className={styles.logo}>{logo}</div>} |
|||
<div className={styles.main}> |
|||
<div className={styles.row}> |
|||
<h1 className={styles.title}>{title}</h1> |
|||
{action && <div className={styles.action}>{action}</div>} |
|||
</div> |
|||
<div className={styles.row}> |
|||
{content && <div className={styles.content}>{content}</div>} |
|||
{extraContent && <div className={styles.extraContent}>{extraContent}</div>} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{tabList && tabList.length ? ( |
|||
<Tabs |
|||
className={styles.tabs} |
|||
{...activeKeyProps} |
|||
onChange={this.onChange} |
|||
tabBarExtraContent={tabBarExtraContent} |
|||
> |
|||
{tabList.map(item => ( |
|||
<TabPane tab={item.tab} key={item.key} /> |
|||
))} |
|||
</Tabs> |
|||
) : null} |
|||
</Skeleton> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
@ -1,161 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.pageHeader { |
|||
padding: 16px 32px 0 32px; |
|||
background: @component-background; |
|||
border-bottom: @border-width-base @border-style-base @border-color-split; |
|||
.wide { |
|||
max-width: 1200px; |
|||
margin: auto; |
|||
} |
|||
.detail { |
|||
display: flex; |
|||
} |
|||
|
|||
.row { |
|||
display: flex; |
|||
width: 100%; |
|||
} |
|||
|
|||
.breadcrumb { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.tabs { |
|||
margin: 0 0 0 -8px; |
|||
|
|||
:global { |
|||
// 1px 可以让选中效果显示完成 |
|||
.ant-tabs-bar { |
|||
margin-bottom: 1px; |
|||
border-bottom: none; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.logo { |
|||
flex: 0 1 auto; |
|||
margin-right: 16px; |
|||
padding-top: 1px; |
|||
> img { |
|||
display: block; |
|||
width: 28px; |
|||
height: 28px; |
|||
border-radius: @border-radius-base; |
|||
} |
|||
} |
|||
|
|||
.title { |
|||
color: @heading-color; |
|||
font-weight: 500; |
|||
font-size: 20px; |
|||
} |
|||
|
|||
.action { |
|||
min-width: 266px; |
|||
margin-left: 56px; |
|||
|
|||
:global { |
|||
.ant-btn-group:not(:last-child), |
|||
.ant-btn:not(:last-child) { |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.ant-btn-group > .ant-btn { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.title, |
|||
.content { |
|||
flex: auto; |
|||
} |
|||
|
|||
.action, |
|||
.extraContent, |
|||
.main { |
|||
flex: 0 1 auto; |
|||
} |
|||
|
|||
.main { |
|||
width: 100%; |
|||
} |
|||
|
|||
.title, |
|||
.action { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.logo, |
|||
.content, |
|||
.extraContent { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.action, |
|||
.extraContent { |
|||
text-align: right; |
|||
} |
|||
|
|||
.extraContent { |
|||
min-width: 242px; |
|||
margin-left: 88px; |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-xl) { |
|||
.pageHeader { |
|||
.extraContent { |
|||
margin-left: 44px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-lg) { |
|||
.pageHeader { |
|||
.extraContent { |
|||
margin-left: 20px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-md) { |
|||
.pageHeader { |
|||
.row { |
|||
display: block; |
|||
} |
|||
|
|||
.action, |
|||
.extraContent { |
|||
margin-left: 0; |
|||
text-align: left; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-sm) { |
|||
.pageHeader { |
|||
.detail { |
|||
display: block; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-xs) { |
|||
.pageHeader { |
|||
.action { |
|||
:global { |
|||
.ant-btn-group, |
|||
.ant-btn { |
|||
display: block; |
|||
margin-bottom: 8px; |
|||
} |
|||
.ant-btn-group > .ant-btn { |
|||
display: inline-block; |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
--- |
|||
title: PageHeader |
|||
subtitle: 页头 |
|||
cols: 1 |
|||
order: 11 |
|||
--- |
|||
|
|||
页头用来声明页面的主题,包含了用户所关注的最重要的信息,使用户可以快速理解当前页面是什么以及它的功能。 |
|||
|
|||
## API |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
|----------|------------------------------------------|-------------|-------| |
|||
| title | title 区域 | ReactNode | - | |
|||
| loading | 骨架屏loading状态 | boolean | false | |
|||
| logo | logo区域 | ReactNode | - | |
|||
| action | 操作区,位于 title 行的行尾 | ReactNode | - | |
|||
| home | 默认的主页说明文字 | ReactNode | - | |
|||
| content | 内容区 | ReactNode | - | |
|||
| extraContent | 额外内容区,位于content的右侧 | ReactNode | - | |
|||
| breadcrumbList | 面包屑数据,配置了此属性时 `routes` `params` `location` `breadcrumbNameMap` 无效 | array<{title: ReactNode, href?: string}> | - | |
|||
| hiddenBreadcrumb |隐藏面包屑 | boolean | false | |
|||
| routes | 面包屑相关属性,router 的路由栈信息 | object[] | - | |
|||
| params | 面包屑相关属性,路由的参数 | object | - | |
|||
| location | 面包屑相关属性,当前的路由信息 | object | - | |
|||
| breadcrumbNameMap | 面包屑相关属性,路由的地址-名称映射表 | object | - | |
|||
| tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - | |
|||
| tabActiveKey | 当前高亮的 tab 项 | string | - | |
|||
| tabDefaultActiveKey | 默认高亮的 tab 项 | string | 第一项 | |
|||
| wide | 是否定宽 | boolean | false | |
|||
| onTabChange | 切换面板的回调 | (key) => void | - | |
|||
| itemRender | 自定义节点方法 | (menuItem) => ReactNode | - | |
|||
| linkElement | 定义链接的元素,默认为 `a`,可传入 react-router 的 Link | string\|ReactElement | - | |
|||
|
|||
> 面包屑的配置方式有三种,一是直接配置 `breadcrumbList`,二是结合 `react-router@2` `react-router@3`,配置 `routes` 及 `params` 实现,类似 [面包屑 Demo](https://ant.design/components/breadcrumb-cn/#components-breadcrumb-demo-router),三是结合 `react-router@4`,配置 `location` `breadcrumbNameMap`,优先级依次递减,脚手架中使用最后一种。 对于后两种用法,你也可以将 `routes` `params` 及 `location` `breadcrumbNameMap` 放到 context 中,组件会自动获取。 |
|||
@ -1,43 +0,0 @@ |
|||
import { getBreadcrumb } from './breadcrumb'; |
|||
import { urlToList } from '../_utils/pathTools'; |
|||
|
|||
const routerData = { |
|||
'/dashboard/analysis': { |
|||
name: '分析页', |
|||
}, |
|||
'/userinfo': { |
|||
name: '用户列表', |
|||
}, |
|||
'/userinfo/:id': { |
|||
name: '用户信息', |
|||
}, |
|||
'/userinfo/:id/addr': { |
|||
name: '收货订单', |
|||
}, |
|||
}; |
|||
describe('test getBreadcrumb', () => { |
|||
it('Simple url', () => { |
|||
expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual('分析页'); |
|||
}); |
|||
it('Parameters url', () => { |
|||
expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual('用户信息'); |
|||
}); |
|||
it('The middle parameter url', () => { |
|||
expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual('收货订单'); |
|||
}); |
|||
it('Loop through the parameters', () => { |
|||
const urlNameList = urlToList('/userinfo/2144/addr').map( |
|||
url => getBreadcrumb(routerData, url).name |
|||
); |
|||
expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']); |
|||
}); |
|||
|
|||
it('a path', () => { |
|||
const urlNameList = urlToList('/userinfo').map(url => getBreadcrumb(routerData, url).name); |
|||
expect(urlNameList).toEqual(['用户列表']); |
|||
}); |
|||
it('Secondary path', () => { |
|||
const urlNameList = urlToList('/userinfo/2144').map(url => getBreadcrumb(routerData, url).name); |
|||
expect(urlNameList).toEqual(['用户列表', '用户信息']); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,116 @@ |
|||
import React from 'react'; |
|||
import pathToRegexp from 'path-to-regexp'; |
|||
import Link from 'umi/link'; |
|||
import { FormattedMessage } from 'umi-plugin-react/locale'; |
|||
import { urlToList } from '../_utils/pathTools'; |
|||
|
|||
// 渲染Breadcrumb 子节点
|
|||
// Render the Breadcrumb child node
|
|||
const itemRender = (route, params, routes, paths) => { |
|||
const last = routes.indexOf(route) === routes.length - 1; |
|||
return last || !route.component ? ( |
|||
<span>{route.breadcrumbName}</span> |
|||
) : ( |
|||
<Link to={paths.join('/')}>{route.breadcrumbName}</Link> |
|||
); |
|||
}; |
|||
|
|||
const renderItemLocal = item => { |
|||
if (item.locale) { |
|||
return <FormattedMessage id={item.locale} defaultMessage={item.name} />; |
|||
} |
|||
return item.name; |
|||
}; |
|||
|
|||
export const getBreadcrumb = (breadcrumbNameMap, url) => { |
|||
let breadcrumb = breadcrumbNameMap[url]; |
|||
if (!breadcrumb) { |
|||
Object.keys(breadcrumbNameMap).forEach(item => { |
|||
if (pathToRegexp(item).test(url)) { |
|||
breadcrumb = breadcrumbNameMap[item]; |
|||
} |
|||
}); |
|||
} |
|||
return breadcrumb || {}; |
|||
}; |
|||
|
|||
export const getBreadcrumbProps = props => { |
|||
const { routes, params, location, breadcrumbNameMap } = props; |
|||
return { |
|||
routes, |
|||
params, |
|||
routerLocation: location, |
|||
breadcrumbNameMap, |
|||
}; |
|||
}; |
|||
|
|||
// Generated according to props
|
|||
const conversionFromProps = props => { |
|||
const { breadcrumbList } = props; |
|||
return breadcrumbList.map(item => { |
|||
const { title, href } = item; |
|||
return { |
|||
path: href, |
|||
breadcrumbName: title, |
|||
}; |
|||
}); |
|||
}; |
|||
|
|||
const conversionFromLocation = (routerLocation, breadcrumbNameMap, props) => { |
|||
const { home } = props; |
|||
// Convert the url to an array
|
|||
const pathSnippets = urlToList(routerLocation.pathname); |
|||
// Loop data mosaic routing
|
|||
const extraBreadcrumbItems = pathSnippets.map(url => { |
|||
const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url); |
|||
if (currentBreadcrumb.inherited) { |
|||
return null; |
|||
} |
|||
const name = renderItemLocal(currentBreadcrumb); |
|||
const { hideInBreadcrumb } = currentBreadcrumb; |
|||
return name && !hideInBreadcrumb |
|||
? { |
|||
path: url, |
|||
breadcrumbName: name, |
|||
} |
|||
: null; |
|||
}); |
|||
// Add home breadcrumbs to your head if defined
|
|||
if (home) { |
|||
extraBreadcrumbItems.unshift({ |
|||
path: '/', |
|||
breadcrumbName: home, |
|||
}); |
|||
} |
|||
return extraBreadcrumbItems; |
|||
}; |
|||
|
|||
/** |
|||
* 将参数转化为面包屑 |
|||
* Convert parameters into breadcrumbs |
|||
*/ |
|||
export const conversionBreadcrumbList = props => { |
|||
const { breadcrumbList } = props; |
|||
const { routes, params, routerLocation, breadcrumbNameMap } = getBreadcrumbProps(props); |
|||
if (breadcrumbList && breadcrumbList.length) { |
|||
return conversionFromProps(); |
|||
} |
|||
// 如果传入 routes 和 params 属性
|
|||
// If pass routes and params attributes
|
|||
if (routes && params) { |
|||
return { |
|||
routes: routes.filter(route => route.breadcrumbName), |
|||
params, |
|||
itemRender, |
|||
}; |
|||
} |
|||
// 根据 location 生成 面包屑
|
|||
// Generate breadcrumbs based on location
|
|||
if (routerLocation && routerLocation.pathname) { |
|||
return { |
|||
routes: conversionFromLocation(routerLocation, breadcrumbNameMap, props), |
|||
itemRender, |
|||
}; |
|||
} |
|||
return {}; |
|||
}; |
|||
@ -1,11 +1,110 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.content { |
|||
.children-content { |
|||
margin: 24px 24px 0; |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-sm) { |
|||
.main { |
|||
:global { |
|||
.ant-page-header { |
|||
padding: 16px 32px 0; |
|||
background: #fff; |
|||
border-bottom: 1px solid #e8e8e8; |
|||
} |
|||
} |
|||
|
|||
.wide { |
|||
max-width: 1200px; |
|||
margin: auto; |
|||
} |
|||
.detail { |
|||
display: flex; |
|||
} |
|||
|
|||
.row { |
|||
display: flex; |
|||
width: 100%; |
|||
} |
|||
|
|||
.logo { |
|||
flex: 0 1 auto; |
|||
margin-right: 16px; |
|||
padding-top: 1px; |
|||
> img { |
|||
display: block; |
|||
width: 28px; |
|||
height: 28px; |
|||
border-radius: @border-radius-base; |
|||
} |
|||
} |
|||
|
|||
.title-content { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-sm) { |
|||
.content { |
|||
margin: 24px 0 0; |
|||
} |
|||
} |
|||
|
|||
.title, |
|||
.content { |
|||
margin: 24px 0 0; |
|||
flex: auto; |
|||
} |
|||
|
|||
.extraContent, |
|||
.main { |
|||
flex: 0 1 auto; |
|||
} |
|||
|
|||
.main { |
|||
width: 100%; |
|||
} |
|||
|
|||
.title { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.logo, |
|||
.content, |
|||
.extraContent { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.extraContent { |
|||
min-width: 242px; |
|||
margin-left: 88px; |
|||
text-align: right; |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-xl) { |
|||
.extraContent { |
|||
margin-left: 44px; |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-lg) { |
|||
.extraContent { |
|||
margin-left: 20px; |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-md) { |
|||
.row { |
|||
display: block; |
|||
} |
|||
|
|||
.action, |
|||
.extraContent { |
|||
margin-left: 0; |
|||
text-align: left; |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-sm) { |
|||
.detail { |
|||
display: block; |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue