BasicLayout.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. // lazy load SettingDrawer
  21. const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer'));
  22. const { Content } = Layout;
  23. const query = {
  24. 'screen-xs': {
  25. maxWidth: 575,
  26. },
  27. 'screen-sm': {
  28. minWidth: 576,
  29. maxWidth: 767,
  30. },
  31. 'screen-md': {
  32. minWidth: 768,
  33. maxWidth: 991,
  34. },
  35. 'screen-lg': {
  36. minWidth: 992,
  37. maxWidth: 1199,
  38. },
  39. 'screen-xl': {
  40. minWidth: 1200,
  41. maxWidth: 1599,
  42. },
  43. 'screen-xxl': {
  44. minWidth: 1600,
  45. },
  46. };
  47. class BasicLayout extends React.PureComponent {
  48. constructor(props) {
  49. super(props);
  50. this.getPageTitle = memoizeOne(this.getPageTitle);
  51. this.getBreadcrumbNameMap = memoizeOne(this.getBreadcrumbNameMap, isEqual);
  52. this.breadcrumbNameMap = this.getBreadcrumbNameMap();
  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. this.breadcrumbNameMap = this.getBreadcrumbNameMap();
  75. const { collapsed, isMobile } = this.props;
  76. if (isMobile && !preProps.isMobile && !collapsed) {
  77. this.handleMenuCollapse(false);
  78. }
  79. }
  80. getContext() {
  81. const { location } = this.props;
  82. return {
  83. location,
  84. breadcrumbNameMap: this.breadcrumbNameMap,
  85. };
  86. }
  87. /**
  88. * 获取面包屑映射
  89. * @param {Object} menuData 菜单配置
  90. */
  91. getBreadcrumbNameMap() {
  92. const routerMap = {};
  93. const { menuData } = this.props;
  94. const flattenMenuData = data => {
  95. data.forEach(menuItem => {
  96. if (menuItem.children) {
  97. flattenMenuData(menuItem.children);
  98. }
  99. // Reduce memory usage
  100. routerMap[menuItem.path] = menuItem;
  101. });
  102. };
  103. flattenMenuData(menuData);
  104. return routerMap;
  105. }
  106. matchParamsPath = pathname => {
  107. const pathKey = Object.keys(this.breadcrumbNameMap).find(key =>
  108. pathToRegexp(key).test(pathname)
  109. );
  110. return this.breadcrumbNameMap[pathKey];
  111. };
  112. getPageTitle = pathname => {
  113. const currRouterData = this.matchParamsPath(pathname);
  114. if (!currRouterData) {
  115. return 'Ant Design Pro';
  116. }
  117. const pageName = formatMessage({
  118. id: currRouterData.locale || currRouterData.name,
  119. defaultMessage: currRouterData.name,
  120. });
  121. return `${pageName} - Ant Design Pro`;
  122. };
  123. getLayoutStyle = () => {
  124. const { fixSiderbar, isMobile, collapsed, layout } = this.props;
  125. if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
  126. return {
  127. paddingLeft: collapsed ? '80px' : '256px',
  128. };
  129. }
  130. return null;
  131. };
  132. getContentStyle = () => {
  133. const { fixedHeader } = this.props;
  134. return {
  135. margin: '24px 24px 0',
  136. paddingTop: fixedHeader ? 64 : 0,
  137. };
  138. };
  139. handleMenuCollapse = collapsed => {
  140. const { dispatch } = this.props;
  141. dispatch({
  142. type: 'global/changeLayoutCollapsed',
  143. payload: collapsed,
  144. });
  145. };
  146. renderSettingDrawer = () => {
  147. // Do not render SettingDrawer in production
  148. // unless it is deployed in preview.pro.ant.design as demo
  149. if (process.env.NODE_ENV === 'production' && APP_TYPE !== 'site') {
  150. return null;
  151. }
  152. return <SettingDrawer />;
  153. };
  154. render() {
  155. const {
  156. navTheme,
  157. layout: PropsLayout,
  158. children,
  159. location: { pathname },
  160. isMobile,
  161. menuData,
  162. } = this.props;
  163. const isTop = PropsLayout === 'topmenu';
  164. const routerConfig = this.matchParamsPath(pathname);
  165. const layout = (
  166. <Layout>
  167. {isTop && !isMobile ? null : (
  168. <SiderMenu
  169. logo={logo}
  170. theme={navTheme}
  171. onCollapse={this.handleMenuCollapse}
  172. menuData={menuData}
  173. isMobile={isMobile}
  174. {...this.props}
  175. />
  176. )}
  177. <Layout
  178. style={{
  179. ...this.getLayoutStyle(),
  180. minHeight: '100vh',
  181. }}
  182. >
  183. <Header
  184. menuData={menuData}
  185. handleMenuCollapse={this.handleMenuCollapse}
  186. logo={logo}
  187. isMobile={isMobile}
  188. {...this.props}
  189. />
  190. <Content style={this.getContentStyle()}>
  191. <Authorized
  192. authority={routerConfig && routerConfig.authority}
  193. noMatch={<Exception403 />}
  194. >
  195. {children}
  196. </Authorized>
  197. </Content>
  198. <Footer />
  199. </Layout>
  200. </Layout>
  201. );
  202. return (
  203. <React.Fragment>
  204. <DocumentTitle title={this.getPageTitle(pathname)}>
  205. <ContainerQuery query={query}>
  206. {params => (
  207. <Context.Provider value={this.getContext()}>
  208. <div className={classNames(params)}>{layout}</div>
  209. </Context.Provider>
  210. )}
  211. </ContainerQuery>
  212. </DocumentTitle>
  213. <Suspense fallback={<PageLoading />}>{this.renderSettingDrawer()}</Suspense>
  214. </React.Fragment>
  215. );
  216. }
  217. }
  218. export default connect(({ global, setting, menu }) => ({
  219. collapsed: global.collapsed,
  220. layout: setting.layout,
  221. menuData: menu.menuData,
  222. ...setting,
  223. }))(props => (
  224. <Media query="(max-width: 599px)">
  225. {isMobile => <BasicLayout {...props} isMobile={isMobile} />}
  226. </Media>
  227. ));