初始化 antd-pro
This commit is contained in:
203
admin-web/src/layouts/BasicLayout.js
Normal file
203
admin-web/src/layouts/BasicLayout.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Layout } from 'antd';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import { connect } from 'dva';
|
||||
import { ContainerQuery } from 'react-container-query';
|
||||
import classNames from 'classnames';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
import Media from 'react-media';
|
||||
import Authorized from '@/utils/Authorized';
|
||||
import logo from '../assets/logo.svg';
|
||||
import Footer from './Footer';
|
||||
import Header from './Header';
|
||||
import Context from './MenuContext';
|
||||
import Exception403 from '../pages/Exception/403';
|
||||
import PageLoading from '@/components/PageLoading';
|
||||
import SiderMenu from '@/components/SiderMenu';
|
||||
import getPageTitle from '@/utils/getPageTitle';
|
||||
import styles from './BasicLayout.less';
|
||||
|
||||
// lazy load SettingDrawer
|
||||
const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer'));
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
const query = {
|
||||
'screen-xs': {
|
||||
maxWidth: 575,
|
||||
},
|
||||
'screen-sm': {
|
||||
minWidth: 576,
|
||||
maxWidth: 767,
|
||||
},
|
||||
'screen-md': {
|
||||
minWidth: 768,
|
||||
maxWidth: 991,
|
||||
},
|
||||
'screen-lg': {
|
||||
minWidth: 992,
|
||||
maxWidth: 1199,
|
||||
},
|
||||
'screen-xl': {
|
||||
minWidth: 1200,
|
||||
maxWidth: 1599,
|
||||
},
|
||||
'screen-xxl': {
|
||||
minWidth: 1600,
|
||||
},
|
||||
};
|
||||
|
||||
class BasicLayout extends React.Component {
|
||||
componentDidMount() {
|
||||
const {
|
||||
dispatch,
|
||||
route: { routes, authority },
|
||||
} = this.props;
|
||||
dispatch({
|
||||
type: 'user/fetchCurrent',
|
||||
});
|
||||
dispatch({
|
||||
type: 'setting/getSetting',
|
||||
});
|
||||
dispatch({
|
||||
type: 'menu/getMenuData',
|
||||
payload: { routes, authority },
|
||||
});
|
||||
}
|
||||
|
||||
getContext() {
|
||||
const { location, breadcrumbNameMap } = this.props;
|
||||
return {
|
||||
location,
|
||||
breadcrumbNameMap,
|
||||
};
|
||||
}
|
||||
|
||||
getRouteAuthority = (pathname, routeData) => {
|
||||
const routes = routeData.slice(); // clone
|
||||
|
||||
const getAuthority = (routeDatas, path) => {
|
||||
let authorities;
|
||||
routeDatas.forEach(route => {
|
||||
// check partial route
|
||||
if (pathToRegexp(`${route.path}(.*)`).test(path)) {
|
||||
if (route.authority) {
|
||||
authorities = route.authority;
|
||||
}
|
||||
// is exact route?
|
||||
if (!pathToRegexp(route.path).test(path) && route.routes) {
|
||||
authorities = getAuthority(route.routes, path);
|
||||
}
|
||||
}
|
||||
});
|
||||
return authorities;
|
||||
};
|
||||
|
||||
return getAuthority(routes, pathname);
|
||||
};
|
||||
|
||||
getLayoutStyle = () => {
|
||||
const { fixSiderbar, isMobile, collapsed, layout } = this.props;
|
||||
if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
|
||||
return {
|
||||
paddingLeft: collapsed ? '80px' : '256px',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
handleMenuCollapse = collapsed => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: 'global/changeLayoutCollapsed',
|
||||
payload: collapsed,
|
||||
});
|
||||
};
|
||||
|
||||
renderSettingDrawer = () => {
|
||||
// Do not render SettingDrawer in production
|
||||
// unless it is deployed in preview.pro.ant.design as demo
|
||||
if (process.env.NODE_ENV === 'production' && APP_TYPE !== 'site') {
|
||||
return null;
|
||||
}
|
||||
return <SettingDrawer />;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
navTheme,
|
||||
layout: PropsLayout,
|
||||
children,
|
||||
location: { pathname },
|
||||
isMobile,
|
||||
menuData,
|
||||
breadcrumbNameMap,
|
||||
route: { routes },
|
||||
fixedHeader,
|
||||
} = this.props;
|
||||
|
||||
const isTop = PropsLayout === 'topmenu';
|
||||
const routerConfig = this.getRouteAuthority(pathname, routes);
|
||||
const contentStyle = !fixedHeader ? { paddingTop: 0 } : {};
|
||||
const layout = (
|
||||
<Layout>
|
||||
{isTop && !isMobile ? null : (
|
||||
<SiderMenu
|
||||
logo={logo}
|
||||
theme={navTheme}
|
||||
onCollapse={this.handleMenuCollapse}
|
||||
menuData={menuData}
|
||||
isMobile={isMobile}
|
||||
{...this.props}
|
||||
/>
|
||||
)}
|
||||
<Layout
|
||||
style={{
|
||||
...this.getLayoutStyle(),
|
||||
minHeight: '100vh',
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
menuData={menuData}
|
||||
handleMenuCollapse={this.handleMenuCollapse}
|
||||
logo={logo}
|
||||
isMobile={isMobile}
|
||||
{...this.props}
|
||||
/>
|
||||
<Content className={styles.content} style={contentStyle}>
|
||||
<Authorized authority={routerConfig} noMatch={<Exception403 />}>
|
||||
{children}
|
||||
</Authorized>
|
||||
</Content>
|
||||
<Footer />
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DocumentTitle title={getPageTitle(pathname, breadcrumbNameMap)}>
|
||||
<ContainerQuery query={query}>
|
||||
{params => (
|
||||
<Context.Provider value={this.getContext()}>
|
||||
<div className={classNames(params)}>{layout}</div>
|
||||
</Context.Provider>
|
||||
)}
|
||||
</ContainerQuery>
|
||||
</DocumentTitle>
|
||||
<Suspense fallback={<PageLoading />}>{this.renderSettingDrawer()}</Suspense>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ global, setting, menu: menuModel }) => ({
|
||||
collapsed: global.collapsed,
|
||||
layout: setting.layout,
|
||||
menuData: menuModel.menuData,
|
||||
breadcrumbNameMap: menuModel.breadcrumbNameMap,
|
||||
...setting,
|
||||
}))(props => (
|
||||
<Media query="(max-width: 599px)">
|
||||
{isMobile => <BasicLayout {...props} isMobile={isMobile} />}
|
||||
</Media>
|
||||
));
|
||||
6
admin-web/src/layouts/BasicLayout.less
Normal file
6
admin-web/src/layouts/BasicLayout.less
Normal file
@@ -0,0 +1,6 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.content {
|
||||
margin: 24px;
|
||||
padding-top: @layout-header-height;
|
||||
}
|
||||
3
admin-web/src/layouts/BlankLayout.js
Normal file
3
admin-web/src/layouts/BlankLayout.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
export default ({ children }) => <div>{children}</div>;
|
||||
37
admin-web/src/layouts/Footer.js
Normal file
37
admin-web/src/layouts/Footer.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Layout, Icon } from 'antd';
|
||||
import GlobalFooter from '@/components/GlobalFooter';
|
||||
|
||||
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" /> 2018 蚂蚁金服体验技术部出品
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Footer>
|
||||
);
|
||||
export default FooterView;
|
||||
161
admin-web/src/layouts/Header.js
Normal file
161
admin-web/src/layouts/Header.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import React, { Component } from 'react';
|
||||
import { formatMessage } from 'umi/locale';
|
||||
import { Layout, message } from 'antd';
|
||||
import Animate from 'rc-animate';
|
||||
import { connect } from 'dva';
|
||||
import router from 'umi/router';
|
||||
import GlobalHeader from '@/components/GlobalHeader';
|
||||
import TopNavHeader from '@/components/TopNavHeader';
|
||||
import styles from './Header.less';
|
||||
|
||||
const { Header } = Layout;
|
||||
|
||||
class HeaderView extends Component {
|
||||
state = {
|
||||
visible: true,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
if (!props.autoHideHeader && !state.visible) {
|
||||
return {
|
||||
visible: true,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 => {
|
||||
message.success(
|
||||
`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${formatMessage({
|
||||
id: `component.globalHeader.${type}`,
|
||||
})}`
|
||||
);
|
||||
const { dispatch } = this.props;
|
||||
dispatch({
|
||||
type: 'global/clearNotices',
|
||||
payload: type,
|
||||
});
|
||||
};
|
||||
|
||||
handleMenuClick = ({ key }) => {
|
||||
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 => {
|
||||
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 }} 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 }) => ({
|
||||
currentUser: user.currentUser,
|
||||
collapsed: global.collapsed,
|
||||
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
|
||||
fetchingNotices: loading.effects['global/fetchNotices'],
|
||||
loadedAllNotices: global.loadedAllNotices,
|
||||
notices: global.notices,
|
||||
setting,
|
||||
}))(HeaderView);
|
||||
8
admin-web/src/layouts/Header.less
Normal file
8
admin-web/src/layouts/Header.less
Normal file
@@ -0,0 +1,8 @@
|
||||
.fixedHeader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: 100%;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
3
admin-web/src/layouts/MenuContext.js
Normal file
3
admin-web/src/layouts/MenuContext.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export default createContext();
|
||||
83
admin-web/src/layouts/UserLayout.js
Normal file
83
admin-web/src/layouts/UserLayout.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { formatMessage } from 'umi/locale';
|
||||
import { connect } from 'dva';
|
||||
import Link from 'umi/link';
|
||||
import { Icon } from 'antd';
|
||||
import GlobalFooter from '@/components/GlobalFooter';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import SelectLang from '@/components/SelectLang';
|
||||
import styles from './UserLayout.less';
|
||||
import logo from '../assets/logo.svg';
|
||||
import getPageTitle from '@/utils/getPageTitle';
|
||||
|
||||
const links = [
|
||||
{
|
||||
key: 'help',
|
||||
title: formatMessage({ id: 'layout.user.link.help' }),
|
||||
href: '',
|
||||
},
|
||||
{
|
||||
key: 'privacy',
|
||||
title: formatMessage({ id: 'layout.user.link.privacy' }),
|
||||
href: '',
|
||||
},
|
||||
{
|
||||
key: 'terms',
|
||||
title: formatMessage({ id: 'layout.user.link.terms' }),
|
||||
href: '',
|
||||
},
|
||||
];
|
||||
|
||||
const copyright = (
|
||||
<Fragment>
|
||||
Copyright <Icon type="copyright" /> 2018 蚂蚁金服体验技术部出品
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
class UserLayout extends Component {
|
||||
componentDidMount() {
|
||||
const {
|
||||
dispatch,
|
||||
route: { routes, authority },
|
||||
} = this.props;
|
||||
dispatch({
|
||||
type: 'menu/getMenuData',
|
||||
payload: { routes, authority },
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
location: { pathname },
|
||||
breadcrumbNameMap,
|
||||
} = this.props;
|
||||
return (
|
||||
<DocumentTitle title={getPageTitle(pathname, breadcrumbNameMap)}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.lang}>
|
||||
<SelectLang />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<Link to="/">
|
||||
<img alt="logo" className={styles.logo} src={logo} />
|
||||
<span className={styles.title}>Ant Design</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
<GlobalFooter links={links} copyright={copyright} />
|
||||
</div>
|
||||
</DocumentTitle>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(({ menu: menuModel }) => ({
|
||||
menuData: menuModel.menuData,
|
||||
breadcrumbNameMap: menuModel.breadcrumbNameMap,
|
||||
}))(UserLayout);
|
||||
71
admin-web/src/layouts/UserLayout.less
Normal file
71
admin-web/src/layouts/UserLayout.less
Normal file
@@ -0,0 +1,71 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background: @layout-body-background;
|
||||
}
|
||||
|
||||
.lang {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: right;
|
||||
:global(.ant-dropdown-trigger) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
.container {
|
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px 0 24px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 44px;
|
||||
margin-right: 16px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
color: @heading-color;
|
||||
font-weight: 600;
|
||||
font-size: 33px;
|
||||
font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
Reference in New Issue
Block a user