BasicLayout.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import React, { Suspense } from 'react';
  2. import { Layout } from 'antd';
  3. import DocumentTitle from 'react-document-title';
  4. import isEqual from 'lodash/isEqual';
  5. import memoizeOne from 'memoize-one';
  6. import { connect } from 'dva';
  7. import { ContainerQuery } from 'react-container-query';
  8. import classNames from 'classnames';
  9. import pathToRegexp from 'path-to-regexp';
  10. import Media from 'react-media';
  11. import { formatMessage } from 'umi/locale';
  12. import Authorized from '@/utils/Authorized';
  13. import logo from '../assets/logo.svg';
  14. import Footer from './Footer';
  15. import Header from './Header';
  16. import Context from './MenuContext';
  17. import Exception403 from '../pages/Exception/403';
  18. import PageLoading from '@/components/PageLoading';
  19. import SiderMenu from '@/components/SiderMenu';
  20. import { menu, title } from '../defaultSettings';
  21. import styles from './BasicLayout.less';
  22. // lazy load SettingDrawer
  23. const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer'));
  24. const { Content } = Layout;
  25. const query = {
  26. 'screen-xs': {
  27. maxWidth: 575,
  28. },
  29. 'screen-sm': {
  30. minWidth: 576,
  31. maxWidth: 767,
  32. },
  33. 'screen-md': {
  34. minWidth: 768,
  35. maxWidth: 991,
  36. },
  37. 'screen-lg': {
  38. minWidth: 992,
  39. maxWidth: 1199,
  40. },
  41. 'screen-xl': {
  42. minWidth: 1200,
  43. maxWidth: 1599,
  44. },
  45. 'screen-xxl': {
  46. minWidth: 1600,
  47. },
  48. };
  49. class BasicLayout extends React.Component {
  50. constructor(props) {
  51. super(props);
  52. this.getPageTitle = memoizeOne(this.getPageTitle);
  53. this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
  54. }
  55. componentDidMount() {
  56. const {
  57. dispatch,
  58. route: { routes, authority },
  59. } = this.props;
  60. dispatch({
  61. type: 'user/fetchCurrent',
  62. });
  63. dispatch({
  64. type: 'setting/getSetting',
  65. });
  66. dispatch({
  67. type: 'menu/getMenuData',
  68. payload: { routes, authority },
  69. });
  70. }
  71. componentDidUpdate(preProps) {
  72. // After changing to phone mode,
  73. // if collapsed is true, you need to click twice to display
  74. const { collapsed, isMobile } = this.props;
  75. if (isMobile && !preProps.isMobile && !collapsed) {
  76. this.handleMenuCollapse(false);
  77. }
  78. }
  79. getContext() {
  80. const { location, breadcrumbNameMap } = this.props;
  81. return {
  82. location,
  83. breadcrumbNameMap,
  84. };
  85. }
  86. matchParamsPath = (pathname, breadcrumbNameMap) => {
  87. const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname));
  88. return breadcrumbNameMap[pathKey];
  89. };
  90. getRouterAuthority = (pathname, routeData) => {
  91. let routeAuthority = ['noAuthority'];
  92. const getAuthority = (key, routes) => {
  93. routes.forEach(route => {
  94. if (route.path && pathToRegexp(route.path).test(key)) {
  95. routeAuthority = route.authority;
  96. } else if (route.routes) {
  97. routeAuthority = getAuthority(key, route.routes);
  98. }
  99. return route;
  100. });
  101. return routeAuthority;
  102. };
  103. return getAuthority(pathname, routeData);
  104. };
  105. getPageTitle = (pathname, breadcrumbNameMap) => {
  106. const currRouterData = this.matchParamsPath(pathname, breadcrumbNameMap);
  107. if (!currRouterData) {
  108. return title;
  109. }
  110. const pageName = menu.disableLocal
  111. ? currRouterData.name
  112. : formatMessage({
  113. id: currRouterData.locale || currRouterData.name,
  114. defaultMessage: currRouterData.name,
  115. });
  116. return `${pageName} - ${title}`;
  117. };
  118. getLayoutStyle = () => {
  119. const { fixSiderbar, isMobile, collapsed, layout } = this.props;
  120. if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
  121. return {
  122. paddingLeft: collapsed ? '80px' : '256px',
  123. };
  124. }
  125. return null;
  126. };
  127. handleMenuCollapse = collapsed => {
  128. const { dispatch } = this.props;
  129. dispatch({
  130. type: 'global/changeLayoutCollapsed',
  131. payload: collapsed,
  132. });
  133. };
  134. renderSettingDrawer = () => {
  135. // Do not render SettingDrawer in production
  136. // unless it is deployed in preview.pro.ant.design as demo
  137. if (process.env.NODE_ENV === 'production' && APP_TYPE !== 'site') {
  138. return null;
  139. }
  140. return <SettingDrawer />;
  141. };
  142. render() {
  143. const {
  144. navTheme,
  145. layout: PropsLayout,
  146. children,
  147. location: { pathname },
  148. isMobile,
  149. menuData,
  150. breadcrumbNameMap,
  151. route: { routes },
  152. fixedHeader,
  153. } = this.props;
  154. const isTop = PropsLayout === 'topmenu';
  155. const routerConfig = this.getRouterAuthority(pathname, routes);
  156. const contentStyle = !fixedHeader ? { paddingTop: 0 } : {};
  157. const layout = (
  158. <Layout>
  159. {isTop && !isMobile ? null : (
  160. <SiderMenu
  161. logo={logo}
  162. theme={navTheme}
  163. onCollapse={this.handleMenuCollapse}
  164. menuData={menuData}
  165. isMobile={isMobile}
  166. {...this.props}
  167. />
  168. )}
  169. <Layout
  170. style={{
  171. ...this.getLayoutStyle(),
  172. minHeight: '100vh',
  173. }}
  174. >
  175. <Header
  176. menuData={menuData}
  177. handleMenuCollapse={this.handleMenuCollapse}
  178. logo={logo}
  179. isMobile={isMobile}
  180. {...this.props}
  181. />
  182. <Content className={styles.content} style={contentStyle}>
  183. <Authorized authority={routerConfig} noMatch={<Exception403 />}>
  184. {children}
  185. </Authorized>
  186. </Content>
  187. <Footer />
  188. </Layout>
  189. </Layout>
  190. );
  191. return (
  192. <React.Fragment>
  193. <DocumentTitle title={this.getPageTitle(pathname, breadcrumbNameMap)}>
  194. <ContainerQuery query={query}>
  195. {params => (
  196. <Context.Provider value={this.getContext()}>
  197. <div className={classNames(params)}>{layout}</div>
  198. </Context.Provider>
  199. )}
  200. </ContainerQuery>
  201. </DocumentTitle>
  202. <Suspense fallback={<PageLoading />}>{this.renderSettingDrawer()}</Suspense>
  203. </React.Fragment>
  204. );
  205. }
  206. }
  207. export default connect(({ global, setting, menu: menuModel }) => ({
  208. collapsed: global.collapsed,
  209. layout: setting.layout,
  210. menuData: menuModel.menuData,
  211. breadcrumbNameMap: menuModel.breadcrumbNameMap,
  212. ...setting,
  213. }))(props => (
  214. <Media query="(max-width: 599px)">
  215. {isMobile => <BasicLayout {...props} isMobile={isMobile} />}
  216. </Media>
  217. ));