8 changed files with 289 additions and 234 deletions
@ -1,116 +0,0 @@ |
|||||
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 {}; |
|
||||
}; |
|
||||
@ -0,0 +1,135 @@ |
|||||
|
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'; |
||||
|
import { PageHeaderWrapperProps } from './'; |
||||
|
import { MenuDataItem } from '../SiderMenu'; |
||||
|
import { BreadcrumbProps as AntdBreadcrumbProps } from 'antd/lib/breadcrumb'; |
||||
|
|
||||
|
type BreadcrumbProps = PageHeaderWrapperProps; |
||||
|
|
||||
|
// 渲染Breadcrumb 子节点
|
||||
|
// Render the Breadcrumb child node
|
||||
|
const itemRender: AntdBreadcrumbProps['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: MenuDataItem): React.ReactNode => { |
||||
|
if (item.locale) { |
||||
|
return <FormattedMessage id={item.locale} defaultMessage={item.name} />; |
||||
|
} |
||||
|
return item.name; |
||||
|
}; |
||||
|
|
||||
|
export const getBreadcrumb = ( |
||||
|
breadcrumbNameMap: PageHeaderWrapperProps['breadcrumbNameMap'], |
||||
|
url: string, |
||||
|
): MenuDataItem => { |
||||
|
if (!breadcrumbNameMap) { |
||||
|
return { |
||||
|
path: '', |
||||
|
}; |
||||
|
} |
||||
|
let breadcrumb = breadcrumbNameMap[url]; |
||||
|
if (!breadcrumb) { |
||||
|
Object.keys(breadcrumbNameMap).forEach(item => { |
||||
|
if (pathToRegexp(item).test(url)) { |
||||
|
breadcrumb = breadcrumbNameMap[item]; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
return breadcrumb || { path: '' }; |
||||
|
}; |
||||
|
|
||||
|
export const getBreadcrumbProps = (props: BreadcrumbProps): PageHeaderWrapperProps => { |
||||
|
const { location, breadcrumbNameMap } = props; |
||||
|
return { |
||||
|
location, |
||||
|
breadcrumbNameMap, |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
// Generated according to props
|
||||
|
const conversionFromProps = (props: BreadcrumbProps): AntdBreadcrumbProps['routes'] => { |
||||
|
const { breadcrumbList = [] } = props; |
||||
|
return breadcrumbList |
||||
|
.map(item => { |
||||
|
const { title, href } = item; |
||||
|
return { |
||||
|
path: href, |
||||
|
breadcrumbName: title, |
||||
|
}; |
||||
|
}) |
||||
|
.filter(item => item.path); |
||||
|
}; |
||||
|
|
||||
|
const conversionFromLocation = ( |
||||
|
routerLocation: PageHeaderWrapperProps['location'], |
||||
|
breadcrumbNameMap: PageHeaderWrapperProps['breadcrumbNameMap'], |
||||
|
props: BreadcrumbProps, |
||||
|
): AntdBreadcrumbProps['routes'] => { |
||||
|
if (!routerLocation) { |
||||
|
return []; |
||||
|
} |
||||
|
const { home } = props; |
||||
|
// Convert the url to an array
|
||||
|
const pathSnippets = urlToList(routerLocation.pathname); |
||||
|
// Loop data mosaic routing
|
||||
|
const extraBreadcrumbItems: AntdBreadcrumbProps['routes'] = pathSnippets |
||||
|
.map(url => { |
||||
|
const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url); |
||||
|
if (currentBreadcrumb.inherited) { |
||||
|
return { path: '', breadcrumbName: '' }; |
||||
|
} |
||||
|
const name = renderItemLocal(currentBreadcrumb); |
||||
|
const { hideInBreadcrumb } = currentBreadcrumb; |
||||
|
return name && !hideInBreadcrumb |
||||
|
? { |
||||
|
path: url, |
||||
|
breadcrumbName: name, |
||||
|
} |
||||
|
: { path: '', breadcrumbName: '' }; |
||||
|
}) |
||||
|
.filter(item => item && item.path); |
||||
|
// 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: BreadcrumbProps): AntdBreadcrumbProps => { |
||||
|
const { breadcrumbList } = props; |
||||
|
const { location, breadcrumbNameMap } = getBreadcrumbProps(props); |
||||
|
if (breadcrumbList && breadcrumbList.length) { |
||||
|
return { |
||||
|
routes: conversionFromProps(props), |
||||
|
itemRender, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 根据 location 生成 面包屑
|
||||
|
// Generate breadcrumbs based on location
|
||||
|
if (location && location.pathname) { |
||||
|
return { |
||||
|
routes: conversionFromLocation(location, breadcrumbNameMap, props), |
||||
|
itemRender, |
||||
|
}; |
||||
|
} |
||||
|
return { |
||||
|
routes: [], |
||||
|
}; |
||||
|
}; |
||||
@ -1,104 +0,0 @@ |
|||||
import React from 'react'; |
|
||||
import { FormattedMessage } from 'umi-plugin-react/locale'; |
|
||||
import Link from 'umi/link'; |
|
||||
import { PageHeader, Tabs, Typography } from 'antd'; |
|
||||
import { connect } from 'dva'; |
|
||||
import classNames from 'classnames'; |
|
||||
import GridContent from './GridContent'; |
|
||||
import styles from './index.less'; |
|
||||
import MenuContext from '@/layouts/MenuContext'; |
|
||||
import { conversionBreadcrumbList } from './breadcrumb'; |
|
||||
|
|
||||
const { Title } = Typography; |
|
||||
|
|
||||
/** |
|
||||
* render Footer tabList |
|
||||
* In order to be compatible with the old version of the PageHeader |
|
||||
* basically all the functions are implemented. |
|
||||
*/ |
|
||||
const renderFooter = ({ tabList, activeKeyProps, onTabChange, tabBarExtraContent }) => { |
|
||||
return tabList && tabList.length ? ( |
|
||||
<Tabs |
|
||||
className={styles.tabs} |
|
||||
{...activeKeyProps} |
|
||||
onChange={key => { |
|
||||
if (onTabChange) { |
|
||||
onTabChange(key); |
|
||||
} |
|
||||
}} |
|
||||
tabBarExtraContent={tabBarExtraContent} |
|
||||
> |
|
||||
{tabList.map(item => ( |
|
||||
<Tabs.TabPane tab={item.tab} key={item.key} /> |
|
||||
))} |
|
||||
</Tabs> |
|
||||
) : null; |
|
||||
}; |
|
||||
|
|
||||
const PageHeaderWrapper = ({ |
|
||||
children, |
|
||||
contentWidth, |
|
||||
wrapperClassName, |
|
||||
top, |
|
||||
title, |
|
||||
content, |
|
||||
logo, |
|
||||
extraContent, |
|
||||
...restProps |
|
||||
}) => { |
|
||||
return ( |
|
||||
<div style={{ margin: '-24px -24px 0' }} className={classNames(classNames, styles.main)}> |
|
||||
{top} |
|
||||
{title && content && ( |
|
||||
<MenuContext.Consumer> |
|
||||
{value => { |
|
||||
return ( |
|
||||
<PageHeader |
|
||||
wide={contentWidth === 'Fixed'} |
|
||||
title={ |
|
||||
<Title |
|
||||
level={4} |
|
||||
style={{ |
|
||||
marginBottom: 0, |
|
||||
}} |
|
||||
> |
|
||||
{title} |
|
||||
</Title> |
|
||||
} |
|
||||
key="pageheader" |
|
||||
{...restProps} |
|
||||
breadcrumb={conversionBreadcrumbList({ |
|
||||
...value, |
|
||||
...restProps, |
|
||||
home: <FormattedMessage id="menu.home" defaultMessage="Home" />, |
|
||||
})} |
|
||||
className={styles.pageHeader} |
|
||||
linkElement={Link} |
|
||||
footer={renderFooter(restProps)} |
|
||||
> |
|
||||
<div className={styles.detail}> |
|
||||
{logo && <div className={styles.logo}>{logo}</div>} |
|
||||
<div className={styles.main}> |
|
||||
<div className={styles.row}> |
|
||||
{content && <div className={styles.content}>{content}</div>} |
|
||||
{extraContent && <div className={styles.extraContent}>{extraContent}</div>} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</PageHeader> |
|
||||
); |
|
||||
}} |
|
||||
</MenuContext.Consumer> |
|
||||
)} |
|
||||
{children ? ( |
|
||||
<div className={styles['children-content']}> |
|
||||
<GridContent>{children}</GridContent> |
|
||||
</div> |
|
||||
) : null} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default connect(({ setting }) => ({ |
|
||||
contentWidth: setting.contentWidth, |
|
||||
}))(PageHeaderWrapper); |
|
||||
@ -0,0 +1,137 @@ |
|||||
|
import React from 'react'; |
||||
|
import { FormattedMessage } from 'umi-plugin-react/locale'; |
||||
|
import { PageHeader, Tabs, Typography } from 'antd'; |
||||
|
import { connect } from 'dva'; |
||||
|
import classNames from 'classnames'; |
||||
|
import GridContent from './GridContent'; |
||||
|
import ConnectState from '@/models/connect'; |
||||
|
import { ContentWidth } from 'config/defaultSettings'; |
||||
|
import styles from './index.less'; |
||||
|
import { conversionBreadcrumbList } from './breadcrumb'; |
||||
|
import { MenuDataItem } from '../SiderMenu'; |
||||
|
import * as H from 'history'; |
||||
|
|
||||
|
const { Title } = Typography; |
||||
|
|
||||
|
/** |
||||
|
* render Footer tabList |
||||
|
* In order to be compatible with the old version of the PageHeader |
||||
|
* basically all the functions are implemented. |
||||
|
*/ |
||||
|
const renderFooter = ({ |
||||
|
tabList, |
||||
|
onTabChange, |
||||
|
tabBarExtraContent, |
||||
|
}: Partial<PageHeaderWrapperProps>) => { |
||||
|
return tabList && tabList.length ? ( |
||||
|
<Tabs |
||||
|
className={styles.tabs} |
||||
|
onChange={key => { |
||||
|
if (onTabChange) { |
||||
|
onTabChange(key); |
||||
|
} |
||||
|
}} |
||||
|
tabBarExtraContent={tabBarExtraContent} |
||||
|
> |
||||
|
{tabList.map(item => ( |
||||
|
<Tabs.TabPane tab={item.tab} key={item.key} /> |
||||
|
))} |
||||
|
</Tabs> |
||||
|
) : null; |
||||
|
}; |
||||
|
|
||||
|
export interface PageHeaderWrapperProps { |
||||
|
title?: React.ReactNode | string | number; |
||||
|
logo?: React.ReactNode | string; |
||||
|
action?: React.ReactNode | string; |
||||
|
content?: React.ReactNode; |
||||
|
extraContent?: React.ReactNode; |
||||
|
breadcrumbList?: Array<{ title: string | number; href: string }>; |
||||
|
tabList?: Array<{ key: string; tab: React.ReactNode }>; |
||||
|
tabActiveKey?: string; |
||||
|
onTabChange?: (key: string) => void; |
||||
|
tabBarExtraContent?: React.ReactNode; |
||||
|
style?: React.CSSProperties; |
||||
|
home?: React.ReactNode; |
||||
|
wide?: boolean; |
||||
|
contentWidth?: ContentWidth; |
||||
|
className?: string; |
||||
|
children?: React.ReactNode; |
||||
|
wrapperClassName?: string; |
||||
|
top?: React.ReactNode; |
||||
|
location?: H.Location; |
||||
|
breadcrumbNameMap?: { [path: string]: MenuDataItem }; |
||||
|
} |
||||
|
|
||||
|
class PageHeaderWrapper extends React.Component<PageHeaderWrapperProps> { |
||||
|
mergePropsAndChildren = (): PageHeaderWrapperProps => { |
||||
|
return { |
||||
|
...this.props, |
||||
|
}; |
||||
|
}; |
||||
|
renderPageHeader = () => { |
||||
|
const { |
||||
|
children, |
||||
|
contentWidth, |
||||
|
wrapperClassName, |
||||
|
top, |
||||
|
title, |
||||
|
content, |
||||
|
logo, |
||||
|
extraContent, |
||||
|
...restProps |
||||
|
} = this.mergePropsAndChildren(); |
||||
|
if (!title && !content) { |
||||
|
return; |
||||
|
} |
||||
|
return ( |
||||
|
<PageHeader |
||||
|
title={ |
||||
|
<Title |
||||
|
level={4} |
||||
|
style={{ |
||||
|
marginBottom: 0, |
||||
|
}} |
||||
|
> |
||||
|
{title} |
||||
|
</Title> |
||||
|
} |
||||
|
{...restProps} |
||||
|
breadcrumb={conversionBreadcrumbList({ |
||||
|
...restProps, |
||||
|
home: <FormattedMessage id="menu.home" defaultMessage="Home" />, |
||||
|
})} |
||||
|
className={styles.pageHeader} |
||||
|
footer={renderFooter(restProps)} |
||||
|
> |
||||
|
<div className={styles.detail}> |
||||
|
{logo && <div className={styles.logo}>{logo}</div>} |
||||
|
<div className={styles.main}> |
||||
|
<div className={styles.row}> |
||||
|
{content && <div className={styles.content}>{content}</div>} |
||||
|
{extraContent && <div className={styles.extraContent}>{extraContent}</div>} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</PageHeader> |
||||
|
); |
||||
|
}; |
||||
|
render() { |
||||
|
const { children, top } = this.mergePropsAndChildren(); |
||||
|
return ( |
||||
|
<div style={{ margin: '-24px -24px 0' }} className={classNames(classNames, styles.main)}> |
||||
|
{top} |
||||
|
{this.renderPageHeader()} |
||||
|
{children ? ( |
||||
|
<div className={styles['children-content']}> |
||||
|
<GridContent>{children}</GridContent> |
||||
|
</div> |
||||
|
) : null} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default connect(({ setting }: ConnectState) => ({ |
||||
|
contentWidth: setting.contentWidth, |
||||
|
}))(PageHeaderWrapper); |
||||
@ -1,3 +0,0 @@ |
|||||
import { createContext } from 'react'; |
|
||||
|
|
||||
export default createContext({}); |
|
||||
Loading…
Reference in new issue