22 changed files with 39 additions and 1527 deletions
@ -1,53 +0,0 @@ |
|||
import React, { Component } from 'react'; |
|||
import { Icon } from 'antd'; |
|||
import Link from 'umi/link'; |
|||
import debounce from 'lodash/debounce'; |
|||
import styles from './index.less'; |
|||
import RightContent, { GlobalHeaderRightProps } from './RightContent'; |
|||
|
|||
type PartialGlobalHeaderRightProps = { |
|||
[K in |
|||
| 'onMenuClick' |
|||
| 'onNoticeClear' |
|||
| 'onNoticeVisibleChange' |
|||
| 'currentUser']?: GlobalHeaderRightProps[K] |
|||
}; |
|||
|
|||
export interface GlobalHeaderProps extends PartialGlobalHeaderRightProps { |
|||
collapsed?: boolean; |
|||
onCollapse?: (collapsed: boolean) => void; |
|||
isMobile?: boolean; |
|||
logo?: string; |
|||
} |
|||
|
|||
export default class GlobalHeader extends Component<GlobalHeaderProps> { |
|||
triggerResizeEvent = debounce(() => { |
|||
const event = document.createEvent('HTMLEvents'); |
|||
event.initEvent('resize', true, false); |
|||
window.dispatchEvent(event); |
|||
}); |
|||
componentWillUnmount() { |
|||
this.triggerResizeEvent.cancel(); |
|||
} |
|||
toggle = () => { |
|||
const { collapsed, onCollapse } = this.props; |
|||
if (onCollapse) onCollapse(!collapsed); |
|||
this.triggerResizeEvent(); |
|||
}; |
|||
render() { |
|||
const { collapsed, isMobile, logo } = this.props; |
|||
return ( |
|||
<div className={styles.header}> |
|||
{isMobile && ( |
|||
<Link to="/" className={styles.logo} key="logo"> |
|||
<img src={logo} alt="logo" width="32" /> |
|||
</Link> |
|||
)} |
|||
<span className={styles.trigger} onClick={this.toggle}> |
|||
<Icon type={collapsed ? 'menu-unfold' : 'menu-fold'} /> |
|||
</span> |
|||
<RightContent {...this.props} /> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
import { Icon } from 'antd'; |
|||
import defaultSettings from '../../../config/defaultSettings'; |
|||
|
|||
const { iconfontUrl } = defaultSettings; |
|||
const scriptUrl = iconfontUrl; |
|||
// 使用:
|
|||
// import IconFont from '@/components/IconFont';
|
|||
// <IconFont type='icon-demo' className='xxx-xxx' />
|
|||
export default Icon.createFromIconfontCN({ scriptUrl }); |
|||
@ -1,31 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Tooltip, Icon } from 'antd'; |
|||
import style from './index.less'; |
|||
|
|||
export interface BlockChecboxProps { |
|||
value: string; |
|||
onChange: (key: string) => void; |
|||
list: any[]; |
|||
} |
|||
|
|||
const BlockChecbox: React.FC<BlockChecboxProps> = ({ value, onChange, list }) => ( |
|||
<div className={style.blockChecbox} key={value}> |
|||
{list.map(item => ( |
|||
<Tooltip title={item.title} key={item.key}> |
|||
<div className={style.item} onClick={() => onChange(item.key)}> |
|||
<img src={item.url} alt={item.key} /> |
|||
<div |
|||
className={style.selectIcon} |
|||
style={{ |
|||
display: value === item.key ? 'block' : 'none', |
|||
}} |
|||
> |
|||
<Icon type="check" /> |
|||
</div> |
|||
</div> |
|||
</Tooltip> |
|||
))} |
|||
</div> |
|||
); |
|||
|
|||
export default BlockChecbox; |
|||
@ -1,21 +0,0 @@ |
|||
.themeColor { |
|||
margin-top: 24px; |
|||
overflow: hidden; |
|||
.title { |
|||
margin-bottom: 12px; |
|||
color: rgba(0, 0, 0, 0.65); |
|||
font-size: 14px; |
|||
line-height: 22px; |
|||
} |
|||
.colorBlock { |
|||
float: left; |
|||
width: 20px; |
|||
height: 20px; |
|||
margin-right: 8px; |
|||
color: #fff; |
|||
font-weight: bold; |
|||
text-align: center; |
|||
border-radius: 2px; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
@ -1,80 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Tooltip, Icon } from 'antd'; |
|||
import { formatMessage } from 'umi-plugin-locale'; |
|||
import styles from './ThemeColor.less'; |
|||
|
|||
export interface TagProps { |
|||
color: string; |
|||
check: boolean; |
|||
className?: string; |
|||
onClick?: () => void; |
|||
} |
|||
|
|||
const Tag: React.FC<TagProps> = ({ color, check, ...rest }) => ( |
|||
<div {...rest} style={{ backgroundColor: color }}> |
|||
{check ? <Icon type="check" /> : ''} |
|||
</div> |
|||
); |
|||
|
|||
export interface ThemeColorProps { |
|||
colors?: any[]; |
|||
title?: string; |
|||
value: string; |
|||
onChange: (color: string) => void; |
|||
} |
|||
|
|||
const ThemeColor: React.FC<ThemeColorProps> = ({ colors, title, value, onChange }) => { |
|||
const colorList = colors || [ |
|||
{ |
|||
key: 'dust', |
|||
color: '#F5222D', |
|||
}, |
|||
{ |
|||
key: 'volcano', |
|||
color: '#FA541C', |
|||
}, |
|||
{ |
|||
key: 'sunset', |
|||
color: '#FAAD14', |
|||
}, |
|||
{ |
|||
key: 'cyan', |
|||
color: '#13C2C2', |
|||
}, |
|||
{ |
|||
key: 'green', |
|||
color: '#52C41A', |
|||
}, |
|||
{ |
|||
key: 'daybreak', |
|||
color: '#1890FF', |
|||
}, |
|||
{ |
|||
key: 'geekblue', |
|||
color: '#2F54EB', |
|||
}, |
|||
{ |
|||
key: 'purple', |
|||
color: '#722ED1', |
|||
}, |
|||
]; |
|||
return ( |
|||
<div className={styles.themeColor}> |
|||
<h3 className={styles.title}>{title}</h3> |
|||
<div className={styles.content}> |
|||
{colorList.map(({ key, color }) => ( |
|||
<Tooltip key={color} title={formatMessage({ id: `app.setting.themecolor.${key}` })}> |
|||
<Tag |
|||
className={styles.colorBlock} |
|||
color={color} |
|||
check={value === color} |
|||
onClick={() => onChange && onChange(color)} |
|||
/> |
|||
</Tooltip> |
|||
))} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default ThemeColor; |
|||
@ -1,81 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.content { |
|||
position: relative; |
|||
min-height: 100%; |
|||
background: #fff; |
|||
:global { |
|||
.ant-list-item { |
|||
span { |
|||
flex: 1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.blockChecbox { |
|||
display: flex; |
|||
.item { |
|||
position: relative; |
|||
margin-right: 16px; |
|||
// box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); |
|||
border-radius: @border-radius-base; |
|||
cursor: pointer; |
|||
img { |
|||
width: 48px; |
|||
} |
|||
} |
|||
.selectIcon { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
padding-top: 15px; |
|||
padding-left: 24px; |
|||
color: @primary-color; |
|||
font-weight: bold; |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.color_block { |
|||
display: inline-block; |
|||
width: 38px; |
|||
height: 22px; |
|||
margin: 4px; |
|||
margin-right: 12px; |
|||
vertical-align: middle; |
|||
border-radius: 4px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.title { |
|||
margin-bottom: 12px; |
|||
color: @heading-color; |
|||
font-size: 14px; |
|||
line-height: 22px; |
|||
} |
|||
|
|||
.handle { |
|||
position: absolute; |
|||
top: 240px; |
|||
right: 300px; |
|||
z-index: 0; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
width: 48px; |
|||
height: 48px; |
|||
font-size: 16px; |
|||
text-align: center; |
|||
background: @primary-color; |
|||
border-radius: 4px 0 0 4px; |
|||
cursor: pointer; |
|||
pointer-events: auto; |
|||
} |
|||
|
|||
.productionHint { |
|||
margin-top: 16px; |
|||
font-size: 12px; |
|||
} |
|||
@ -1,272 +0,0 @@ |
|||
import { ConnectProps, ConnectState, SettingModelState } from '@/models/connect'; |
|||
import React, { Component } from 'react'; |
|||
import { Select, message, Drawer, List, Switch, Divider, Icon, Button, Alert, Tooltip } from 'antd'; |
|||
import { formatMessage } from 'umi-plugin-react/locale'; |
|||
import { CopyToClipboard } from 'react-copy-to-clipboard'; |
|||
import { connect } from 'dva'; |
|||
import omit from 'omit.js'; |
|||
import styles from './index.less'; |
|||
import ThemeColor from './ThemeColor'; |
|||
import BlockCheckbox from './BlockCheckbox'; |
|||
|
|||
const { Option } = Select; |
|||
interface BodyProps { |
|||
title: string; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
const Body: React.FC<BodyProps> = ({ children, title, style }) => ( |
|||
<div style={{ ...style, marginBottom: 24 }}> |
|||
<h3 className={styles.title}>{title}</h3> |
|||
{children} |
|||
</div> |
|||
); |
|||
|
|||
interface SettingItemProps { |
|||
title: React.ReactNode; |
|||
action: React.ReactElement; |
|||
disabled?: boolean; |
|||
disabledReason?: React.ReactNode; |
|||
} |
|||
|
|||
export interface SettingDrawerProps extends ConnectProps { |
|||
setting?: SettingModelState; |
|||
} |
|||
|
|||
export interface SettingDrawerState extends Partial<SettingModelState> { |
|||
collapse: boolean; |
|||
} |
|||
|
|||
@connect(({ setting }: ConnectState) => ({ setting })) |
|||
class SettingDrawer extends Component<SettingDrawerProps, SettingDrawerState> { |
|||
state: SettingDrawerState = { |
|||
collapse: false, |
|||
}; |
|||
|
|||
getLayoutSetting = (): SettingItemProps[] => { |
|||
const { setting } = this.props; |
|||
const { contentWidth, fixedHeader, layout, autoHideHeader, fixSiderbar } = setting!; |
|||
return [ |
|||
{ |
|||
title: formatMessage({ id: 'app.setting.content-width' }), |
|||
action: ( |
|||
<Select |
|||
value={contentWidth} |
|||
size="small" |
|||
onSelect={value => this.changeSetting('contentWidth', value)} |
|||
style={{ width: 80 }} |
|||
> |
|||
{layout === 'sidemenu' ? null : ( |
|||
<Option value="Fixed"> |
|||
{formatMessage({ id: 'app.setting.content-width.fixed' })} |
|||
</Option> |
|||
)} |
|||
<Option value="Fluid"> |
|||
{formatMessage({ id: 'app.setting.content-width.fluid' })} |
|||
</Option> |
|||
</Select> |
|||
), |
|||
}, |
|||
{ |
|||
title: formatMessage({ id: 'app.setting.fixedheader' }), |
|||
action: ( |
|||
<Switch |
|||
size="small" |
|||
checked={!!fixedHeader} |
|||
onChange={checked => this.changeSetting('fixedHeader', checked)} |
|||
/> |
|||
), |
|||
}, |
|||
{ |
|||
title: formatMessage({ id: 'app.setting.hideheader' }), |
|||
disabled: !fixedHeader, |
|||
disabledReason: formatMessage({ id: 'app.setting.hideheader.hint' }), |
|||
action: ( |
|||
<Switch |
|||
size="small" |
|||
checked={!!autoHideHeader} |
|||
onChange={checked => this.changeSetting('autoHideHeader', checked)} |
|||
/> |
|||
), |
|||
}, |
|||
{ |
|||
title: formatMessage({ id: 'app.setting.fixedsidebar' }), |
|||
disabled: layout === 'topmenu', |
|||
disabledReason: formatMessage({ id: 'app.setting.fixedsidebar.hint' }), |
|||
action: ( |
|||
<Switch |
|||
size="small" |
|||
checked={!!fixSiderbar} |
|||
onChange={checked => this.changeSetting('fixSiderbar', checked)} |
|||
/> |
|||
), |
|||
}, |
|||
]; |
|||
}; |
|||
|
|||
changeSetting = (key: string, value: any) => { |
|||
const { setting } = this.props; |
|||
const nextState = { ...setting! }; |
|||
nextState[key] = value; |
|||
if (key === 'layout') { |
|||
nextState.contentWidth = value === 'topmenu' ? 'Fixed' : 'Fluid'; |
|||
} else if (key === 'fixedHeader' && !value) { |
|||
nextState.autoHideHeader = false; |
|||
} |
|||
this.setState(nextState, () => { |
|||
const { dispatch } = this.props; |
|||
dispatch!({ |
|||
type: 'setting/changeSetting', |
|||
payload: this.state, |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
togglerContent = () => { |
|||
const { collapse } = this.state; |
|||
this.setState({ collapse: !collapse }); |
|||
}; |
|||
|
|||
renderLayoutSettingItem = (item: SettingItemProps) => { |
|||
const action = React.cloneElement(item.action, { |
|||
disabled: item.disabled, |
|||
}); |
|||
return ( |
|||
<Tooltip title={item.disabled ? item.disabledReason : ''} placement="left"> |
|||
<List.Item actions={[action]}> |
|||
<span style={{ opacity: item.disabled ? 0.5 : 1 }}>{item.title}</span> |
|||
</List.Item> |
|||
</Tooltip> |
|||
); |
|||
}; |
|||
|
|||
render() { |
|||
const { setting } = this.props; |
|||
const { navTheme, primaryColor, layout, colorWeak } = setting!; |
|||
const { collapse } = this.state; |
|||
return ( |
|||
<Drawer |
|||
visible={collapse} |
|||
width={300} |
|||
onClose={this.togglerContent} |
|||
placement="right" |
|||
handler={ |
|||
<div className={styles.handle} onClick={this.togglerContent}> |
|||
<Icon |
|||
type={collapse ? 'close' : 'setting'} |
|||
style={{ |
|||
color: '#fff', |
|||
fontSize: 20, |
|||
}} |
|||
/> |
|||
</div> |
|||
} |
|||
style={{ |
|||
zIndex: 999, |
|||
}} |
|||
> |
|||
<div className={styles.content}> |
|||
<Body title={formatMessage({ id: 'app.setting.pagestyle' })}> |
|||
<BlockCheckbox |
|||
list={[ |
|||
{ |
|||
key: 'dark', |
|||
url: 'https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg', |
|||
title: formatMessage({ id: 'app.setting.pagestyle.dark' }), |
|||
}, |
|||
{ |
|||
key: 'light', |
|||
url: 'https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg', |
|||
title: formatMessage({ id: 'app.setting.pagestyle.light' }), |
|||
}, |
|||
]} |
|||
value={navTheme} |
|||
onChange={value => this.changeSetting('navTheme', value)} |
|||
/> |
|||
</Body> |
|||
|
|||
<ThemeColor |
|||
title={formatMessage({ id: 'app.setting.themecolor' })} |
|||
value={primaryColor} |
|||
onChange={color => this.changeSetting('primaryColor', color)} |
|||
/> |
|||
|
|||
<Divider /> |
|||
|
|||
<Body title={formatMessage({ id: 'app.setting.navigationmode' })}> |
|||
<BlockCheckbox |
|||
list={[ |
|||
{ |
|||
key: 'sidemenu', |
|||
url: 'https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg', |
|||
title: formatMessage({ id: 'app.setting.sidemenu' }), |
|||
}, |
|||
{ |
|||
key: 'topmenu', |
|||
url: 'https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg', |
|||
title: formatMessage({ id: 'app.setting.topmenu' }), |
|||
}, |
|||
]} |
|||
value={layout} |
|||
onChange={value => this.changeSetting('layout', value)} |
|||
/> |
|||
</Body> |
|||
|
|||
<List |
|||
split={false} |
|||
dataSource={this.getLayoutSetting()} |
|||
renderItem={this.renderLayoutSettingItem} |
|||
/> |
|||
|
|||
<Divider /> |
|||
|
|||
<Body title={formatMessage({ id: 'app.setting.othersettings' })}> |
|||
<List |
|||
split={false} |
|||
renderItem={this.renderLayoutSettingItem} |
|||
dataSource={[ |
|||
{ |
|||
title: formatMessage({ id: 'app.setting.weakmode' }), |
|||
action: ( |
|||
<Switch |
|||
size="small" |
|||
checked={!!colorWeak} |
|||
onChange={checked => this.changeSetting('colorWeak', checked)} |
|||
/> |
|||
), |
|||
}, |
|||
]} |
|||
/> |
|||
</Body> |
|||
<Divider /> |
|||
<CopyToClipboard |
|||
text={JSON.stringify(omit(setting, ['colorWeak']), null, 2)} |
|||
onCopy={() => message.success(formatMessage({ id: 'app.setting.copyinfo' }))} |
|||
> |
|||
<Button block icon="copy"> |
|||
{formatMessage({ id: 'app.setting.copy' })} |
|||
</Button> |
|||
</CopyToClipboard> |
|||
<Alert |
|||
type="warning" |
|||
className={styles.productionHint} |
|||
message={ |
|||
<div> |
|||
{formatMessage({ id: 'app.setting.production.hint' })}{' '} |
|||
<a |
|||
href="https://u.ant.design/pro-v2-default-settings" |
|||
target="_blank" |
|||
rel="noopener noreferrer" |
|||
> |
|||
src/defaultSettings.js |
|||
</a> |
|||
</div> |
|||
} |
|||
/> |
|||
</div> |
|||
</Drawer> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default SettingDrawer; |
|||
@ -1,206 +0,0 @@ |
|||
import IconFont from '@/components/IconFont'; |
|||
import { isUrl } from '@/utils/utils'; |
|||
import { Icon, Menu } from 'antd'; |
|||
import { MenuMode, MenuTheme } from 'antd/es/menu'; |
|||
import classNames from 'classnames'; |
|||
import React, { Component } from 'react'; |
|||
import { RouterTypes } from 'umi'; |
|||
import Link from 'umi/link'; |
|||
import { urlToList } from '../_utils/pathTools'; |
|||
import styles from './index.less'; |
|||
import { getMenuMatches } from './SiderMenuUtils'; |
|||
|
|||
const { SubMenu } = Menu; |
|||
|
|||
// Allow menu.js config icon as string or ReactNode
|
|||
// icon: 'setting',
|
|||
// icon: 'icon-geren' #For Iconfont ,
|
|||
// icon: 'http://demo.com/icon.png',
|
|||
// icon: <Icon type="setting" />,
|
|||
const getIcon = (icon?: string | React.ReactNode) => { |
|||
if (typeof icon === 'string') { |
|||
if (isUrl(icon)) { |
|||
return <Icon component={() => <img src={icon} alt="icon" className={styles.icon} />} />; |
|||
} |
|||
if (icon.startsWith('icon-')) { |
|||
return <IconFont type={icon} />; |
|||
} |
|||
return <Icon type={icon} />; |
|||
} |
|||
return icon; |
|||
}; |
|||
|
|||
export interface MenuDataItem { |
|||
authority?: string[] | string; |
|||
children?: MenuDataItem[]; |
|||
hideChildrenInMenu?: boolean; |
|||
hideInMenu?: boolean; |
|||
icon?: string; |
|||
locale?: string; |
|||
name?: string; |
|||
path: string; |
|||
[key: string]: any; |
|||
} |
|||
|
|||
export interface Route extends MenuDataItem { |
|||
routes?: Route[]; |
|||
} |
|||
|
|||
export interface BaseMenuProps extends Partial<RouterTypes<Route>> { |
|||
className?: string; |
|||
collapsed?: boolean; |
|||
flatMenuKeys?: any[]; |
|||
handleOpenChange?: (openKeys: string[]) => void; |
|||
isMobile?: boolean; |
|||
menuData?: MenuDataItem[]; |
|||
mode?: MenuMode; |
|||
onCollapse?: (collapsed: boolean) => void; |
|||
onOpenChange?: (openKeys: string[]) => void; |
|||
openKeys?: string[]; |
|||
style?: React.CSSProperties; |
|||
theme?: MenuTheme; |
|||
} |
|||
|
|||
export default class BaseMenu extends Component<BaseMenuProps> { |
|||
static defaultProps: Partial<BaseMenuProps> = { |
|||
flatMenuKeys: [], |
|||
onCollapse: () => void 0, |
|||
isMobile: false, |
|||
openKeys: [], |
|||
collapsed: false, |
|||
handleOpenChange: () => void 0, |
|||
menuData: [], |
|||
onOpenChange: () => void 0, |
|||
}; |
|||
|
|||
/** |
|||
* 获得菜单子节点 |
|||
*/ |
|||
getNavMenuItems = (menusData: MenuDataItem[] = []): React.ReactNode[] => { |
|||
return menusData |
|||
.filter(item => item.name && !item.hideInMenu) |
|||
.map(item => this.getSubMenuOrItem(item)) |
|||
.filter(item => item); |
|||
}; |
|||
|
|||
// Get the currently selected menu
|
|||
getSelectedMenuKeys = (pathname: string): string[] => { |
|||
const { flatMenuKeys } = this.props; |
|||
return urlToList(pathname) |
|||
.map(itemPath => getMenuMatches(flatMenuKeys, itemPath).pop()) |
|||
.filter(item => item) as string[]; |
|||
}; |
|||
|
|||
/** |
|||
* get SubMenu or Item |
|||
*/ |
|||
getSubMenuOrItem = (item: MenuDataItem): React.ReactNode => { |
|||
if ( |
|||
Array.isArray(item.children) && |
|||
!item.hideChildrenInMenu && |
|||
item.children.some(child => (child.name ? true : false)) |
|||
) { |
|||
return ( |
|||
<SubMenu |
|||
title={ |
|||
item.icon ? ( |
|||
<span> |
|||
{getIcon(item.icon)} |
|||
<span>{item.name}</span> |
|||
</span> |
|||
) : ( |
|||
item.name |
|||
) |
|||
} |
|||
key={item.path} |
|||
> |
|||
{this.getNavMenuItems(item.children)} |
|||
</SubMenu> |
|||
); |
|||
} |
|||
return <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>; |
|||
}; |
|||
|
|||
/** |
|||
* 判断是否是http链接.返回 Link 或 a |
|||
* Judge whether it is http link.return a or Link |
|||
* @memberof SiderMenu |
|||
*/ |
|||
getMenuItemPath = (item: MenuDataItem) => { |
|||
const { name } = item; |
|||
const itemPath = this.conversionPath(item.path); |
|||
const icon = getIcon(item.icon); |
|||
const { target } = item; |
|||
// Is it a http link
|
|||
if (/^https?:\/\//.test(itemPath)) { |
|||
return ( |
|||
<a href={itemPath} target={target}> |
|||
{icon} |
|||
<span>{name}</span> |
|||
</a> |
|||
); |
|||
} |
|||
const { location, isMobile, onCollapse } = this.props; |
|||
return ( |
|||
<Link |
|||
to={itemPath} |
|||
target={target} |
|||
replace={itemPath === location!.pathname} |
|||
onClick={isMobile ? () => onCollapse!(true) : void 0} |
|||
> |
|||
{icon} |
|||
<span>{name}</span> |
|||
</Link> |
|||
); |
|||
}; |
|||
|
|||
conversionPath = (path: string) => { |
|||
if (path && path.indexOf('http') === 0) { |
|||
return path; |
|||
} |
|||
return `/${path || ''}`.replace(/\/+/g, '/'); |
|||
}; |
|||
|
|||
render() { |
|||
const { |
|||
openKeys, |
|||
theme, |
|||
mode, |
|||
location, |
|||
className, |
|||
collapsed, |
|||
handleOpenChange, |
|||
style, |
|||
menuData, |
|||
} = this.props; |
|||
// if pathname can't match, use the nearest parent's key
|
|||
let selectedKeys = this.getSelectedMenuKeys(location!.pathname); |
|||
if (!selectedKeys.length && openKeys) { |
|||
selectedKeys = [openKeys[openKeys.length - 1]]; |
|||
} |
|||
let props = {}; |
|||
if (openKeys && !collapsed) { |
|||
props = { |
|||
openKeys: openKeys.length === 0 ? [...selectedKeys] : openKeys, |
|||
}; |
|||
} |
|||
const cls = classNames(className, { |
|||
'top-nav-menu': mode === 'horizontal', |
|||
}); |
|||
|
|||
return ( |
|||
<Menu |
|||
key="Menu" |
|||
mode={mode} |
|||
theme={theme} |
|||
onOpenChange={handleOpenChange} |
|||
selectedKeys={selectedKeys} |
|||
style={style} |
|||
className={cls} |
|||
{...props} |
|||
> |
|||
{this.getNavMenuItems(menuData)} |
|||
</Menu> |
|||
); |
|||
} |
|||
} |
|||
@ -1,124 +0,0 @@ |
|||
import { Layout } from 'antd'; |
|||
import classNames from 'classnames'; |
|||
import React, { Component, Suspense } from 'react'; |
|||
import Link from 'umi/link'; |
|||
import defaultSettings from '../../../config/defaultSettings'; |
|||
import PageLoading from '../PageLoading'; |
|||
import { BaseMenuProps } from './BaseMenu'; |
|||
import styles from './index.less'; |
|||
import { getDefaultCollapsedSubMenus } from './SiderMenuUtils'; |
|||
|
|||
const BaseMenu = React.lazy(() => import('./BaseMenu')); |
|||
const { Sider } = Layout; |
|||
const { title } = defaultSettings; |
|||
let firstMount: boolean = true; |
|||
|
|||
export interface SiderMenuProps extends BaseMenuProps { |
|||
logo?: string; |
|||
fixSiderbar?: boolean; |
|||
} |
|||
|
|||
interface SiderMenuState { |
|||
pathname?: string; |
|||
openKeys?: string[]; |
|||
flatMenuKeysLen?: number; |
|||
} |
|||
|
|||
export default class SiderMenu extends Component<SiderMenuProps, SiderMenuState> { |
|||
static defaultProps: Partial<SiderMenuProps> = { |
|||
flatMenuKeys: [], |
|||
onCollapse: () => void 0, |
|||
isMobile: false, |
|||
openKeys: [], |
|||
collapsed: false, |
|||
handleOpenChange: () => void 0, |
|||
menuData: [], |
|||
onOpenChange: () => void 0, |
|||
}; |
|||
|
|||
static getDerivedStateFromProps(props: SiderMenuProps, state: SiderMenuState) { |
|||
const { pathname, flatMenuKeysLen } = state; |
|||
if (props.location!.pathname !== pathname || props.flatMenuKeys!.length !== flatMenuKeysLen) { |
|||
return { |
|||
pathname: props.location!.pathname, |
|||
flatMenuKeysLen: props.flatMenuKeys!.length, |
|||
openKeys: getDefaultCollapsedSubMenus(props), |
|||
}; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
constructor(props: SiderMenuProps) { |
|||
super(props); |
|||
this.state = { |
|||
openKeys: getDefaultCollapsedSubMenus(props), |
|||
}; |
|||
} |
|||
|
|||
componentDidMount() { |
|||
firstMount = false; |
|||
} |
|||
|
|||
isMainMenu: (key: string) => boolean = key => { |
|||
const { menuData } = this.props; |
|||
return menuData!.some(item => { |
|||
if (key) { |
|||
return item.key === key || item.path === key; |
|||
} |
|||
return false; |
|||
}); |
|||
}; |
|||
|
|||
handleOpenChange: (openKeys: string[]) => void = openKeys => { |
|||
const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1; |
|||
if (moreThanOne) { |
|||
this.setState({ openKeys: [openKeys.pop()].filter(item => item) as string[] }); |
|||
} else { |
|||
this.setState({ openKeys: [...openKeys] }); |
|||
} |
|||
}; |
|||
|
|||
render() { |
|||
const { logo, collapsed, onCollapse, fixSiderbar, theme, isMobile } = this.props; |
|||
const { openKeys } = this.state; |
|||
const defaultProps = collapsed ? {} : { openKeys }; |
|||
|
|||
const siderClassName = classNames(styles.sider, { |
|||
[styles.fixSiderBar]: fixSiderbar, |
|||
[styles.light]: theme === 'light', |
|||
}); |
|||
return ( |
|||
<Sider |
|||
collapsible |
|||
trigger={null} |
|||
collapsed={collapsed} |
|||
breakpoint="lg" |
|||
onCollapse={collapse => { |
|||
if (firstMount || !isMobile) { |
|||
onCollapse!(collapse); |
|||
} |
|||
}} |
|||
width={256} |
|||
theme={theme} |
|||
className={siderClassName} |
|||
> |
|||
<div className={styles.logo} id="logo"> |
|||
<Link to="/"> |
|||
<img src={logo} alt="logo" /> |
|||
<h1>{title}</h1> |
|||
</Link> |
|||
</div> |
|||
<Suspense fallback={<PageLoading />}> |
|||
<BaseMenu |
|||
{...this.props} |
|||
mode="inline" |
|||
handleOpenChange={this.handleOpenChange} |
|||
onOpenChange={this.handleOpenChange} |
|||
style={{ padding: '16px 0', width: '100%' }} |
|||
{...defaultProps} |
|||
/> |
|||
</Suspense> |
|||
</Sider> |
|||
); |
|||
} |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
import pathToRegexp from 'path-to-regexp'; |
|||
import { urlToList } from '../_utils/pathTools'; |
|||
import { MenuDataItem, BaseMenuProps } from './BaseMenu'; |
|||
|
|||
/** |
|||
* Recursively flatten the data |
|||
* [{path:string},{path:string}] => {path,path2} |
|||
* @param menus |
|||
*/ |
|||
export const getFlatMenuKeys = (menuData: MenuDataItem[] = []) => { |
|||
let keys: string[] = []; |
|||
menuData.forEach(item => { |
|||
keys.push(item.path); |
|||
if (item.children) { |
|||
keys = keys.concat(getFlatMenuKeys(item.children)); |
|||
} |
|||
}); |
|||
return keys; |
|||
}; |
|||
|
|||
export const getMenuMatches = (flatMenuKeys: string[] = [], path: string) => |
|||
flatMenuKeys.filter(item => item && pathToRegexp(item).test(path)); |
|||
|
|||
/** |
|||
* 获得菜单子节点 |
|||
*/ |
|||
export const getDefaultCollapsedSubMenus = (props: BaseMenuProps) => { |
|||
const { location, flatMenuKeys } = props; |
|||
return urlToList(location!.pathname) |
|||
.map(item => getMenuMatches(flatMenuKeys, item)[0]) |
|||
.filter(item => item) |
|||
.reduce((acc, curr) => [...acc, curr], ['/']); |
|||
}; |
|||
@ -1,105 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
@nav-header-height: @layout-header-height; |
|||
|
|||
.logo { |
|||
position: relative; |
|||
height: @nav-header-height; |
|||
padding-left: (@menu-collapsed-width - 32px) / 2; |
|||
overflow: hidden; |
|||
line-height: @nav-header-height; |
|||
background: #002140; |
|||
transition: all 0.3s; |
|||
img { |
|||
display: inline-block; |
|||
height: 32px; |
|||
vertical-align: middle; |
|||
} |
|||
h1 { |
|||
display: inline-block; |
|||
margin: 0 0 0 12px; |
|||
color: white; |
|||
font-weight: 600; |
|||
font-size: 20px; |
|||
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; |
|||
vertical-align: middle; |
|||
} |
|||
} |
|||
.sider { |
|||
position: relative; |
|||
z-index: 10; |
|||
min-height: 100vh; |
|||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); |
|||
&.fixSiderBar { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05); |
|||
:global { |
|||
.ant-menu-root { |
|||
height: ~'calc(100vh - @{nav-header-height})'; |
|||
overflow-y: auto; |
|||
} |
|||
.ant-menu-inline { |
|||
border-right: 0; |
|||
.ant-menu-item, |
|||
.ant-menu-submenu-title { |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
&.light { |
|||
background-color: white; |
|||
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05); |
|||
.logo { |
|||
background: white; |
|||
box-shadow: 1px 1px 0 0 @border-color-split; |
|||
h1 { |
|||
color: @primary-color; |
|||
} |
|||
} |
|||
:global(.ant-menu-light) { |
|||
border-right-color: transparent; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.icon { |
|||
width: 14px; |
|||
vertical-align: baseline; |
|||
} |
|||
|
|||
:global { |
|||
.top-nav-menu li.ant-menu-item { |
|||
height: @nav-header-height; |
|||
line-height: @nav-header-height; |
|||
} |
|||
.drawer .drawer-content { |
|||
background: #001529; |
|||
} |
|||
.ant-menu-inline-collapsed { |
|||
& > .ant-menu-item .sider-menu-item-img + span, |
|||
& |
|||
> .ant-menu-item-group |
|||
> .ant-menu-item-group-list |
|||
> .ant-menu-item |
|||
.sider-menu-item-img |
|||
+ span, |
|||
& > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span { |
|||
display: inline-block; |
|||
max-width: 0; |
|||
opacity: 0; |
|||
} |
|||
} |
|||
.ant-menu-item .sider-menu-item-img + span, |
|||
.ant-menu-submenu-title .sider-menu-item-img + span { |
|||
opacity: 1; |
|||
transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out; |
|||
} |
|||
.ant-drawer-left { |
|||
.ant-drawer-body { |
|||
padding: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Drawer } from 'antd'; |
|||
import { SiderMenuProps } from './SiderMenu'; |
|||
import SiderMenu from './SiderMenu'; |
|||
import { getFlatMenuKeys } from './SiderMenuUtils'; |
|||
|
|||
export { SiderMenuProps }; |
|||
export { MenuDataItem, Route } from './BaseMenu'; |
|||
|
|||
const SiderMenuWrapper: React.FC<SiderMenuProps> = props => { |
|||
const { isMobile, menuData, collapsed, onCollapse } = props; |
|||
const flatMenuKeys = getFlatMenuKeys(menuData); |
|||
return isMobile ? ( |
|||
<Drawer |
|||
visible={!collapsed} |
|||
placement="left" |
|||
onClose={() => onCollapse!(true)} |
|||
style={{ |
|||
padding: 0, |
|||
height: '100vh', |
|||
}} |
|||
> |
|||
<SiderMenu {...props} flatMenuKeys={flatMenuKeys} collapsed={isMobile ? false : collapsed} /> |
|||
</Drawer> |
|||
) : ( |
|||
<SiderMenu {...props} flatMenuKeys={flatMenuKeys} /> |
|||
); |
|||
}; |
|||
|
|||
SiderMenuWrapper.defaultProps = { |
|||
onCollapse: () => void 0, |
|||
}; |
|||
|
|||
export default React.memo(SiderMenuWrapper); |
|||
@ -1,72 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.head { |
|||
position: relative; |
|||
width: 100%; |
|||
height: @layout-header-height; |
|||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); |
|||
transition: background 0.3s, width 0.2s; |
|||
:global { |
|||
.ant-menu-submenu.ant-menu-submenu-horizontal { |
|||
height: 100%; |
|||
line-height: @layout-header-height; |
|||
.ant-menu-submenu-title { |
|||
height: 100%; |
|||
} |
|||
} |
|||
} |
|||
&.light { |
|||
background-color: #fff; |
|||
} |
|||
.main { |
|||
display: flex; |
|||
height: @layout-header-height; |
|||
padding-left: 24px; |
|||
&.wide { |
|||
max-width: 1200px; |
|||
margin: auto; |
|||
padding-left: 0; |
|||
} |
|||
.left { |
|||
display: flex; |
|||
flex: 1; |
|||
} |
|||
.right { |
|||
width: 324px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.logo { |
|||
position: relative; |
|||
width: 165px; |
|||
height: @layout-header-height; |
|||
overflow: hidden; |
|||
line-height: @layout-header-height; |
|||
transition: all 0.3s; |
|||
img { |
|||
display: inline-block; |
|||
height: 32px; |
|||
vertical-align: middle; |
|||
} |
|||
h1 { |
|||
display: inline-block; |
|||
margin: 0 0 0 12px; |
|||
color: #fff; |
|||
font-weight: 400; |
|||
font-size: 16px; |
|||
vertical-align: top; |
|||
} |
|||
} |
|||
|
|||
.light { |
|||
h1 { |
|||
color: #002140; |
|||
} |
|||
} |
|||
|
|||
.menu { |
|||
height: @layout-header-height; |
|||
line-height: @layout-header-height; |
|||
border: none; |
|||
} |
|||
@ -1,59 +0,0 @@ |
|||
import { SiderMenuProps } from '@/components/SiderMenu'; |
|||
import React, { Component } from 'react'; |
|||
import Link from 'umi/link'; |
|||
import RightContent, { GlobalHeaderRightProps } from '../GlobalHeader/RightContent'; |
|||
import BaseMenu from '../SiderMenu/BaseMenu'; |
|||
import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils'; |
|||
import styles from './index.less'; |
|||
import defaultSettings, { ContentWidth } from '../../../config/defaultSettings'; |
|||
|
|||
export interface TopNavHeaderProps extends SiderMenuProps, GlobalHeaderRightProps { |
|||
contentWidth?: ContentWidth; |
|||
} |
|||
|
|||
interface TopNavHeaderState { |
|||
maxWidth?: number; |
|||
} |
|||
|
|||
export default class TopNavHeader extends Component<TopNavHeaderProps, TopNavHeaderState> { |
|||
static getDerivedStateFromProps(props: TopNavHeaderProps) { |
|||
return { |
|||
maxWidth: |
|||
(props.contentWidth === 'Fixed' && window.innerWidth > 1200 ? 1200 : window.innerWidth) - |
|||
280 - |
|||
120 - |
|||
40, |
|||
}; |
|||
} |
|||
|
|||
state: TopNavHeaderState = {}; |
|||
|
|||
maim: HTMLDivElement | null = null; |
|||
|
|||
render() { |
|||
const { theme, contentWidth, menuData, logo } = this.props; |
|||
const { maxWidth } = this.state; |
|||
const flatMenuKeys = getFlatMenuKeys(menuData); |
|||
return ( |
|||
<div className={`${styles.head} ${theme === 'light' ? styles.light : ''}`}> |
|||
<div |
|||
ref={ref => (this.maim = ref)} |
|||
className={`${styles.main} ${contentWidth === 'Fixed' ? styles.wide : ''}`} |
|||
> |
|||
<div className={styles.left}> |
|||
<div className={styles.logo} key="logo" id="logo"> |
|||
<Link to="/"> |
|||
<img src={logo} alt="logo" /> |
|||
<h1>{defaultSettings.title}</h1> |
|||
</Link> |
|||
</div> |
|||
<div style={{ maxWidth }}> |
|||
<BaseMenu {...this.props} flatMenuKeys={flatMenuKeys} className={styles.menu} /> |
|||
</div> |
|||
</div> |
|||
<RightContent {...this.props} /> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.content { |
|||
margin: 24px; |
|||
padding-top: @layout-header-height; |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
import { Icon, Layout } from 'antd'; |
|||
import React, { Fragment } from 'react'; |
|||
import { GlobalFooter } from 'ant-design-pro'; |
|||
|
|||
const { Footer } = Layout; |
|||
const FooterView = () => ( |
|||
<Footer style={{ padding: 0 }}> |
|||
<GlobalFooter |
|||
links={[ |
|||
{ |
|||
key: 'Pro 首页', |
|||
title: 'Pro 首页', |
|||
href: 'https://pro.ant.design', |
|||
blankTarget: true, |
|||
}, |
|||
{ |
|||
key: 'github', |
|||
title: <Icon type="github" />, |
|||
href: 'https://github.com/ant-design/ant-design-pro', |
|||
blankTarget: true, |
|||
}, |
|||
{ |
|||
key: 'Ant Design', |
|||
title: 'Ant Design', |
|||
href: 'https://ant.design', |
|||
blankTarget: true, |
|||
}, |
|||
]} |
|||
copyright={ |
|||
<Fragment> |
|||
Copyright <Icon type="copyright" /> 2019 蚂蚁金服体验技术部出品 |
|||
</Fragment> |
|||
} |
|||
/> |
|||
</Footer> |
|||
); |
|||
export default FooterView; |
|||
@ -1,8 +0,0 @@ |
|||
.fixedHeader { |
|||
position: fixed; |
|||
top: 0; |
|||
right: 0; |
|||
z-index: 9; |
|||
width: 100%; |
|||
transition: width 0.2s; |
|||
} |
|||
@ -1,179 +0,0 @@ |
|||
import GlobalHeader, { GlobalHeaderProps } from '@/components/GlobalHeader'; |
|||
import TopNavHeader, { TopNavHeaderProps } from '@/components/TopNavHeader'; |
|||
import { ConnectProps, ConnectState, SettingModelState } from '@/models/connect'; |
|||
import React, { Component } from 'react'; |
|||
import { formatMessage } from 'umi-plugin-react/locale'; |
|||
import { Layout, message } from 'antd'; |
|||
import { ClickParam } from 'antd/es/menu'; |
|||
import { connect } from 'dva'; |
|||
import Animate from 'rc-animate'; |
|||
import router from 'umi/router'; |
|||
import styles from './Header.less'; |
|||
|
|||
const { Header } = Layout; |
|||
|
|||
export interface HeaderViewProps extends ConnectProps, TopNavHeaderProps, GlobalHeaderProps { |
|||
isMobile?: boolean; |
|||
collapsed?: boolean; |
|||
setting?: SettingModelState; |
|||
autoHideHeader?: boolean; |
|||
handleMenuCollapse?: (collapse: boolean) => void; |
|||
} |
|||
|
|||
interface HeaderViewState { |
|||
visible: boolean; |
|||
} |
|||
|
|||
class HeaderView extends Component<HeaderViewProps, HeaderViewState> { |
|||
static getDerivedStateFromProps(props: HeaderViewProps, state: HeaderViewState) { |
|||
if (!props.autoHideHeader && !state.visible) { |
|||
return { |
|||
visible: true, |
|||
}; |
|||
} |
|||
return null; |
|||
} |
|||
state = { |
|||
visible: true, |
|||
}; |
|||
|
|||
ticking: boolean = false; |
|||
oldScrollTop: number = 0; |
|||
|
|||
componentDidMount() { |
|||
document.addEventListener('scroll', this.handScroll, { passive: true }); |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
document.removeEventListener('scroll', this.handScroll); |
|||
} |
|||
|
|||
getHeadWidth = () => { |
|||
const { isMobile, collapsed, setting } = this.props; |
|||
const { fixedHeader, layout } = setting!; |
|||
if (isMobile || !fixedHeader || layout === 'topmenu') { |
|||
return '100%'; |
|||
} |
|||
return collapsed ? 'calc(100% - 80px)' : 'calc(100% - 256px)'; |
|||
}; |
|||
|
|||
handleNoticeClear = (type: string) => { |
|||
const { dispatch } = this.props; |
|||
message.success( |
|||
`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${formatMessage({ |
|||
id: `component.globalHeader.${type}`, |
|||
})}`,
|
|||
); |
|||
dispatch!({ |
|||
type: 'global/clearNotices', |
|||
payload: type, |
|||
}); |
|||
}; |
|||
|
|||
handleMenuClick = ({ key }: ClickParam) => { |
|||
const { dispatch } = this.props; |
|||
if (key === 'userCenter') { |
|||
router.push('/account/center'); |
|||
return; |
|||
} |
|||
if (key === 'triggerError') { |
|||
router.push('/exception/trigger'); |
|||
return; |
|||
} |
|||
if (key === 'userinfo') { |
|||
router.push('/account/settings/base'); |
|||
return; |
|||
} |
|||
if (key === 'logout') { |
|||
dispatch!({ |
|||
type: 'login/logout', |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
handleNoticeVisibleChange = (visible: boolean) => { |
|||
if (visible) { |
|||
const { dispatch } = this.props; |
|||
dispatch!({ |
|||
type: 'global/fetchNotices', |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
handScroll = () => { |
|||
const { autoHideHeader } = this.props; |
|||
const { visible } = this.state; |
|||
if (!autoHideHeader) { |
|||
return; |
|||
} |
|||
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop; |
|||
if (!this.ticking) { |
|||
this.ticking = true; |
|||
requestAnimationFrame(() => { |
|||
if (this.oldScrollTop > scrollTop) { |
|||
this.setState({ |
|||
visible: true, |
|||
}); |
|||
} else if (scrollTop > 300 && visible) { |
|||
this.setState({ |
|||
visible: false, |
|||
}); |
|||
} else if (scrollTop < 300 && !visible) { |
|||
this.setState({ |
|||
visible: true, |
|||
}); |
|||
} |
|||
this.oldScrollTop = scrollTop; |
|||
this.ticking = false; |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
render() { |
|||
const { isMobile, handleMenuCollapse, setting } = this.props; |
|||
const { navTheme, layout, fixedHeader } = setting!; |
|||
const { visible } = this.state; |
|||
const isTop = layout === 'topmenu'; |
|||
const width = this.getHeadWidth(); |
|||
const HeaderDom = visible ? ( |
|||
<Header |
|||
style={{ padding: 0, width, zIndex: 2 }} |
|||
className={fixedHeader ? styles.fixedHeader : ''} |
|||
> |
|||
{isTop && !isMobile ? ( |
|||
<TopNavHeader |
|||
theme={navTheme} |
|||
mode="horizontal" |
|||
onCollapse={handleMenuCollapse} |
|||
onNoticeClear={this.handleNoticeClear} |
|||
onMenuClick={this.handleMenuClick} |
|||
onNoticeVisibleChange={this.handleNoticeVisibleChange} |
|||
{...this.props} |
|||
/> |
|||
) : ( |
|||
<GlobalHeader |
|||
onCollapse={handleMenuCollapse} |
|||
onNoticeClear={this.handleNoticeClear} |
|||
onMenuClick={this.handleMenuClick} |
|||
onNoticeVisibleChange={this.handleNoticeVisibleChange} |
|||
{...this.props} |
|||
/> |
|||
)} |
|||
</Header> |
|||
) : null; |
|||
return ( |
|||
<Animate component="" transitionName="fade"> |
|||
{HeaderDom} |
|||
</Animate> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default connect(({ user, global, setting, loading }: ConnectState) => ({ |
|||
currentUser: user.currentUser, |
|||
collapsed: global.collapsed, |
|||
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'], |
|||
fetchingNotices: loading.effects['global/fetchNotices'], |
|||
notices: global.notices, |
|||
setting, |
|||
}))(HeaderView); |
|||
Loading…
Reference in new issue