BasicLayout.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import React, { Fragment } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { Layout, Icon, message } from 'antd';
  4. import DocumentTitle from 'react-document-title';
  5. import { connect } from 'dva';
  6. import { Route, Redirect, Switch, routerRedux } from 'dva/router';
  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 GlobalHeader from '../components/GlobalHeader';
  12. import GlobalFooter from '../components/GlobalFooter';
  13. import SiderMenu from '../components/SiderMenu';
  14. import NotFound from '../routes/Exception/404';
  15. import { getRoutes } from '../utils/utils';
  16. import Authorized from '../utils/Authorized';
  17. import { getMenuData } from '../common/menu';
  18. import logo from '../assets/logo.svg';
  19. const { Content, Header, Footer } = Layout;
  20. const { AuthorizedRoute, check } = Authorized;
  21. /**
  22. * 根据菜单取得重定向地址.
  23. */
  24. const redirectData = [];
  25. const getRedirect = item => {
  26. if (item && item.children) {
  27. if (item.children[0] && item.children[0].path) {
  28. redirectData.push({
  29. from: `${item.path}`,
  30. to: `${item.children[0].path}`,
  31. });
  32. item.children.forEach(children => {
  33. getRedirect(children);
  34. });
  35. }
  36. }
  37. };
  38. getMenuData().forEach(getRedirect);
  39. /**
  40. * 获取面包屑映射
  41. * @param {Object} menuData 菜单配置
  42. * @param {Object} routerData 路由配置
  43. */
  44. const getBreadcrumbNameMap = (menuData, routerData) => {
  45. const result = {};
  46. const childResult = {};
  47. for (const i of menuData) {
  48. if (!routerData[i.path]) {
  49. result[i.path] = i;
  50. }
  51. if (i.children) {
  52. Object.assign(childResult, getBreadcrumbNameMap(i.children, routerData));
  53. }
  54. }
  55. return Object.assign({}, routerData, result, childResult);
  56. };
  57. const query = {
  58. 'screen-xs': {
  59. maxWidth: 575,
  60. },
  61. 'screen-sm': {
  62. minWidth: 576,
  63. maxWidth: 767,
  64. },
  65. 'screen-md': {
  66. minWidth: 768,
  67. maxWidth: 991,
  68. },
  69. 'screen-lg': {
  70. minWidth: 992,
  71. maxWidth: 1199,
  72. },
  73. 'screen-xl': {
  74. minWidth: 1200,
  75. },
  76. };
  77. let isMobile;
  78. enquireScreen(b => {
  79. isMobile = b;
  80. });
  81. class BasicLayout extends React.PureComponent {
  82. static childContextTypes = {
  83. location: PropTypes.object,
  84. breadcrumbNameMap: PropTypes.object,
  85. };
  86. state = {
  87. isMobile,
  88. };
  89. getChildContext() {
  90. const { location, routerData } = this.props;
  91. return {
  92. location,
  93. breadcrumbNameMap: getBreadcrumbNameMap(getMenuData(), routerData),
  94. };
  95. }
  96. componentDidMount() {
  97. this.enquireHandler = enquireScreen(mobile => {
  98. this.setState({
  99. isMobile: mobile,
  100. });
  101. });
  102. this.props.dispatch({
  103. type: 'user/fetchCurrent',
  104. });
  105. }
  106. componentWillUnmount() {
  107. unenquireScreen(this.enquireHandler);
  108. }
  109. getPageTitle() {
  110. const { routerData, location } = this.props;
  111. const { pathname } = location;
  112. let title = 'Ant Design Pro';
  113. let currRouterData = null;
  114. // match params path
  115. Object.keys(routerData).forEach(key => {
  116. if (pathToRegexp(key).test(pathname)) {
  117. currRouterData = routerData[key];
  118. }
  119. });
  120. if (currRouterData && currRouterData.name) {
  121. title = `${currRouterData.name} - Ant Design Pro`;
  122. }
  123. return title;
  124. }
  125. getBaseRedirect = () => {
  126. // According to the url parameter to redirect
  127. // 这里是重定向的,重定向到 url 的 redirect 参数所示地址
  128. const urlParams = new URL(window.location.href);
  129. const redirect = urlParams.searchParams.get('redirect');
  130. // Remove the parameters in the url
  131. if (redirect) {
  132. urlParams.searchParams.delete('redirect');
  133. window.history.replaceState(null, 'redirect', urlParams.href);
  134. } else {
  135. const { routerData } = this.props;
  136. // get the first authorized route path in routerData
  137. const authorizedPath = Object.keys(routerData).find(
  138. item => check(routerData[item].authority, item) && item !== '/'
  139. );
  140. return authorizedPath;
  141. }
  142. return redirect;
  143. };
  144. handleMenuCollapse = collapsed => {
  145. this.props.dispatch({
  146. type: 'global/changeLayoutCollapsed',
  147. payload: collapsed,
  148. });
  149. };
  150. handleNoticeClear = type => {
  151. message.success(`清空了${type}`);
  152. this.props.dispatch({
  153. type: 'global/clearNotices',
  154. payload: type,
  155. });
  156. };
  157. handleMenuClick = ({ key }) => {
  158. if (key === 'triggerError') {
  159. this.props.dispatch(routerRedux.push('/exception/trigger'));
  160. return;
  161. }
  162. if (key === 'logout') {
  163. this.props.dispatch({
  164. type: 'login/logout',
  165. });
  166. }
  167. };
  168. handleNoticeVisibleChange = visible => {
  169. if (visible) {
  170. this.props.dispatch({
  171. type: 'global/fetchNotices',
  172. });
  173. }
  174. };
  175. render() {
  176. const {
  177. currentUser,
  178. collapsed,
  179. fetchingNotices,
  180. notices,
  181. routerData,
  182. match,
  183. location,
  184. } = this.props;
  185. const bashRedirect = this.getBaseRedirect();
  186. const layout = (
  187. <Layout>
  188. <SiderMenu
  189. logo={logo}
  190. // 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
  191. // If you do not have the Authorized parameter
  192. // you will be forced to jump to the 403 interface without permission
  193. Authorized={Authorized}
  194. menuData={getMenuData()}
  195. collapsed={collapsed}
  196. location={location}
  197. isMobile={this.state.isMobile}
  198. onCollapse={this.handleMenuCollapse}
  199. />
  200. <Layout>
  201. <Header style={{ padding: 0 }}>
  202. <GlobalHeader
  203. logo={logo}
  204. currentUser={currentUser}
  205. fetchingNotices={fetchingNotices}
  206. notices={notices}
  207. collapsed={collapsed}
  208. isMobile={this.state.isMobile}
  209. onNoticeClear={this.handleNoticeClear}
  210. onCollapse={this.handleMenuCollapse}
  211. onMenuClick={this.handleMenuClick}
  212. onNoticeVisibleChange={this.handleNoticeVisibleChange}
  213. />
  214. </Header>
  215. <Content style={{ margin: '24px 24px 0', height: '100%' }}>
  216. <Switch>
  217. {redirectData.map(item => (
  218. <Redirect key={item.from} exact from={item.from} to={item.to} />
  219. ))}
  220. {getRoutes(match.path, routerData).map(item => (
  221. <AuthorizedRoute
  222. key={item.key}
  223. path={item.path}
  224. component={item.component}
  225. exact={item.exact}
  226. authority={item.authority}
  227. redirectPath="/exception/403"
  228. />
  229. ))}
  230. <Redirect exact from="/" to={bashRedirect} />
  231. <Route render={NotFound} />
  232. </Switch>
  233. </Content>
  234. <Footer style={{ padding: 0 }}>
  235. <GlobalFooter
  236. links={[
  237. {
  238. key: 'Pro 首页',
  239. title: 'Pro 首页',
  240. href: 'http://pro.ant.design',
  241. blankTarget: true,
  242. },
  243. {
  244. key: 'github',
  245. title: <Icon type="github" />,
  246. href: 'https://github.com/ant-design/ant-design-pro',
  247. blankTarget: true,
  248. },
  249. {
  250. key: 'Ant Design',
  251. title: 'Ant Design',
  252. href: 'http://ant.design',
  253. blankTarget: true,
  254. },
  255. ]}
  256. copyright={
  257. <Fragment>
  258. Copyright <Icon type="copyright" /> 2018 蚂蚁金服体验技术部出品
  259. </Fragment>
  260. }
  261. />
  262. </Footer>
  263. </Layout>
  264. </Layout>
  265. );
  266. return (
  267. <DocumentTitle title={this.getPageTitle()}>
  268. <ContainerQuery query={query}>
  269. {params => <div className={classNames(params)}>{layout}</div>}
  270. </ContainerQuery>
  271. </DocumentTitle>
  272. );
  273. }
  274. }
  275. export default connect(({ user, global, loading }) => ({
  276. currentUser: user.currentUser,
  277. collapsed: global.collapsed,
  278. fetchingNotices: loading.effects['global/fetchNotices'],
  279. notices: global.notices,
  280. }))(BasicLayout);