BasicLayout.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import React 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 { enquireScreen, unenquireScreen } from 'enquire-js';
  11. import { formatMessage } from 'umi/locale';
  12. import SiderMenu from '@/components/SiderMenu';
  13. import Authorized from '@/utils/Authorized';
  14. import SettingDrawer from '@/components/SettingDrawer';
  15. import logo from '../assets/logo.svg';
  16. import Footer from './Footer';
  17. import Header from './Header';
  18. import Context from './MenuContext';
  19. const { Content } = Layout;
  20. const { check } = Authorized;
  21. // Conversion router to menu.
  22. function formatter(data, parentPath = '', parentAuthority, parentName) {
  23. return data.map(item => {
  24. let locale = 'menu';
  25. if (parentName && item.name) {
  26. locale = `${parentName}.${item.name}`;
  27. } else if (item.name) {
  28. locale = `menu.${item.name}`;
  29. } else if (parentName) {
  30. locale = parentName;
  31. }
  32. const result = {
  33. ...item,
  34. locale,
  35. authority: item.authority || parentAuthority,
  36. };
  37. if (item.routes) {
  38. const children = formatter(item.routes, `${parentPath}${item.path}/`, item.authority, locale);
  39. // Reduce memory usage
  40. result.children = children;
  41. }
  42. delete result.routes;
  43. return result;
  44. });
  45. }
  46. const query = {
  47. 'screen-xs': {
  48. maxWidth: 575,
  49. },
  50. 'screen-sm': {
  51. minWidth: 576,
  52. maxWidth: 767,
  53. },
  54. 'screen-md': {
  55. minWidth: 768,
  56. maxWidth: 991,
  57. },
  58. 'screen-lg': {
  59. minWidth: 992,
  60. maxWidth: 1199,
  61. },
  62. 'screen-xl': {
  63. minWidth: 1200,
  64. maxWidth: 1599,
  65. },
  66. 'screen-xxl': {
  67. minWidth: 1600,
  68. },
  69. };
  70. class BasicLayout extends React.PureComponent {
  71. constructor(props) {
  72. super(props);
  73. this.getPageTitle = memoizeOne(this.getPageTitle);
  74. this.getBreadcrumbNameMap = memoizeOne(this.getBreadcrumbNameMap, isEqual);
  75. this.breadcrumbNameMap = this.getBreadcrumbNameMap();
  76. }
  77. state = {
  78. rendering: true,
  79. isMobile: false,
  80. };
  81. componentDidMount() {
  82. const { dispatch } = this.props;
  83. dispatch({
  84. type: 'user/fetchCurrent',
  85. });
  86. dispatch({
  87. type: 'setting/getSetting',
  88. });
  89. this.renderRef = requestAnimationFrame(() => {
  90. this.setState({
  91. rendering: false,
  92. });
  93. });
  94. this.enquireHandler = enquireScreen(mobile => {
  95. const { isMobile } = this.state;
  96. if (isMobile !== mobile) {
  97. this.setState({
  98. isMobile: mobile,
  99. });
  100. }
  101. });
  102. }
  103. componentDidUpdate() {
  104. this.breadcrumbNameMap = this.getBreadcrumbNameMap();
  105. }
  106. componentWillUnmount() {
  107. cancelAnimationFrame(this.renderRef);
  108. unenquireScreen(this.enquireHandler);
  109. }
  110. getContext() {
  111. const { location } = this.props;
  112. return {
  113. location,
  114. breadcrumbNameMap: this.breadcrumbNameMap,
  115. };
  116. }
  117. getMenuData() {
  118. const {
  119. route: { routes },
  120. } = this.props;
  121. return formatter(routes);
  122. }
  123. /**
  124. * 获取面包屑映射
  125. * @param {Object} menuData 菜单配置
  126. */
  127. getBreadcrumbNameMap() {
  128. const routerMap = {};
  129. const mergeMenuAndRouter = data => {
  130. data.forEach(menuItem => {
  131. if (menuItem.children) {
  132. mergeMenuAndRouter(menuItem.children);
  133. }
  134. // Reduce memory usage
  135. routerMap[menuItem.path] = menuItem;
  136. });
  137. };
  138. mergeMenuAndRouter(this.getMenuData());
  139. return routerMap;
  140. }
  141. getPageTitle = pathname => {
  142. let currRouterData = null;
  143. // match params path
  144. Object.keys(this.breadcrumbNameMap).forEach(key => {
  145. if (pathToRegexp(key).test(pathname)) {
  146. currRouterData = this.breadcrumbNameMap[key];
  147. }
  148. });
  149. if (!currRouterData) {
  150. return 'Ant Design Pro';
  151. }
  152. const message = formatMessage({
  153. id: currRouterData.locale || currRouterData.name,
  154. defaultMessage: currRouterData.name,
  155. });
  156. return `${message} - Ant Design Pro`;
  157. };
  158. getLayoutStyle = () => {
  159. const { fixSiderbar, collapsed, layout } = this.props;
  160. if (fixSiderbar && layout !== 'topmenu') {
  161. return {
  162. paddingLeft: collapsed ? '80px' : '256px',
  163. };
  164. }
  165. return null;
  166. };
  167. getContentStyle = () => {
  168. const { fixedHeader } = this.props;
  169. return {
  170. margin: '24px 24px 0',
  171. paddingTop: fixedHeader ? 64 : 0,
  172. };
  173. };
  174. getBashRedirect = () => {
  175. // According to the url parameter to redirect
  176. // 这里是重定向的,重定向到 url 的 redirect 参数所示地址
  177. const urlParams = new URL(window.location.href);
  178. const redirect = urlParams.searchParams.get('redirect');
  179. // Remove the parameters in the url
  180. if (redirect) {
  181. urlParams.searchParams.delete('redirect');
  182. window.history.replaceState(null, 'redirect', urlParams.href);
  183. } else {
  184. const { routerData } = this.props;
  185. // get the first authorized route path in routerData
  186. const authorizedPath = Object.keys(routerData).find(
  187. item => check(routerData[item].authority, item) && item !== '/'
  188. );
  189. return authorizedPath;
  190. }
  191. return redirect;
  192. };
  193. handleMenuCollapse = collapsed => {
  194. const { dispatch } = this.props;
  195. dispatch({
  196. type: 'global/changeLayoutCollapsed',
  197. payload: collapsed,
  198. });
  199. };
  200. render() {
  201. const {
  202. navTheme,
  203. layout: PropsLayout,
  204. children,
  205. location: { pathname },
  206. } = this.props;
  207. const { rendering, isMobile } = this.state;
  208. const isTop = PropsLayout === 'topmenu';
  209. const menuData = this.getMenuData();
  210. const layout = (
  211. <Layout>
  212. {isTop && !isMobile ? null : (
  213. <SiderMenu
  214. logo={logo}
  215. Authorized={Authorized}
  216. theme={navTheme}
  217. onCollapse={this.handleMenuCollapse}
  218. menuData={menuData}
  219. isMobile={isMobile}
  220. {...this.props}
  221. />
  222. )}
  223. <Layout
  224. style={{
  225. ...this.getLayoutStyle(),
  226. minHeight: '100vh',
  227. }}
  228. >
  229. <Header
  230. menuData={menuData}
  231. handleMenuCollapse={this.handleMenuCollapse}
  232. logo={logo}
  233. isMobile={isMobile}
  234. {...this.props}
  235. />
  236. <Content style={this.getContentStyle()}>{children}</Content>
  237. <Footer />
  238. </Layout>
  239. </Layout>
  240. );
  241. return (
  242. <React.Fragment>
  243. <DocumentTitle title={this.getPageTitle(pathname)}>
  244. <ContainerQuery query={query}>
  245. {params => (
  246. <Context.Provider value={this.getContext()}>
  247. <div className={classNames(params)}>{layout}</div>
  248. </Context.Provider>
  249. )}
  250. </ContainerQuery>
  251. </DocumentTitle>
  252. {rendering && process.env.NODE_ENV === 'production' ? null : ( // Do show SettingDrawer in production
  253. <SettingDrawer />
  254. )}
  255. </React.Fragment>
  256. );
  257. }
  258. }
  259. export default connect(({ global, setting }) => ({
  260. collapsed: global.collapsed,
  261. layout: setting.layout,
  262. ...setting,
  263. }))(BasicLayout);