BasicLayout.js 6.37 KB
Newer Older
jim's avatar
jim committed
1 2
import React from 'react';
import { Layout } from 'antd';
3
import DocumentTitle from 'react-document-title';
afc163's avatar
afc163 committed
4
import isEqual from 'lodash/isEqual';
5
import memoizeOne from 'memoize-one';
6
import { connect } from 'dva';
afc163's avatar
afc163 committed
7 8
import { ContainerQuery } from 'react-container-query';
import classNames from 'classnames';
jim's avatar
jim committed
9
import pathToRegexp from 'path-to-regexp';
afc163's avatar
afc163 committed
10
import { enquireScreen, unenquireScreen } from 'enquire-js';
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
11
import { formatMessage } from 'umi/locale';
12 13
import SiderMenu from '@/components/SiderMenu';
import Authorized from '@/utils/Authorized';
14
import SettingDrawer from '@/components/SettingDrawer';
15
import logo from '../assets/logo.svg';
jim's avatar
jim committed
16 17
import Footer from './Footer';
import Header from './Header';
18
import Context from './MenuContext';
19

jim's avatar
jim committed
20
const { Content } = Layout;
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
21
const { check } = Authorized;
ddcat1115's avatar
ddcat1115 committed
22

afc163's avatar
afc163 committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
// Conversion router to menu.
function formatter(data, parentPath = '', parentAuthority, parentName) {
  return data.map(item => {
    let locale = 'menu';
    if (parentName && item.name) {
      locale = `${parentName}.${item.name}`;
    } else if (item.name) {
      locale = `menu.${item.name}`;
    } else if (parentName) {
      locale = parentName;
    }
    const result = {
      ...item,
      locale,
      authority: item.authority || parentAuthority,
    };
    if (item.routes) {
      const children = formatter(item.routes, `${parentPath}${item.path}/`, item.authority, locale);
      // Reduce memory usage
      result.children = children;
    }
    delete result.routes;
    return result;
  });
}

afc163's avatar
afc163 committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
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,
yoyo837's avatar
yoyo837 committed
67 68 69 70
    maxWidth: 1599,
  },
  'screen-xxl': {
    minWidth: 1600,
afc163's avatar
afc163 committed
71 72 73
  },
};

74
class BasicLayout extends React.PureComponent {
75 76
  constructor(props) {
    super(props);
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
77
    this.getPageTitle = memoizeOne(this.getPageTitle);
78 79
    this.getBreadcrumbNameMap = memoizeOne(this.getBreadcrumbNameMap, isEqual);
    this.breadcrumbNameMap = this.getBreadcrumbNameMap();
80
  }
81 82 83

  state = {
    rendering: true,
afc163's avatar
afc163 committed
84
    isMobile: false,
85 86 87
  };

  componentDidMount() {
afc163's avatar
afc163 committed
88 89 90 91 92 93 94
    const { dispatch } = this.props;
    dispatch({
      type: 'user/fetchCurrent',
    });
    dispatch({
      type: 'setting/getSetting',
    });
95 96 97 98 99
    this.renderRef = requestAnimationFrame(() => {
      this.setState({
        rendering: false,
      });
    });
afc163's avatar
afc163 committed
100 101 102 103 104 105 106 107
    this.enquireHandler = enquireScreen(mobile => {
      const { isMobile } = this.state;
      if (isMobile !== mobile) {
        this.setState({
          isMobile: mobile,
        });
      }
    });
108 109 110
  }

  componentDidUpdate() {
111
    this.breadcrumbNameMap = this.getBreadcrumbNameMap();
112 113 114 115
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.renderRef);
afc163's avatar
afc163 committed
116
    unenquireScreen(this.enquireHandler);
117 118
  }

jim's avatar
jim committed
119
  getContext() {
120
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
121 122
    return {
      location,
123
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
124
    };
125
  }
126

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
  getMenuData() {
    const {
      route: { routes },
    } = this.props;
    return formatter(routes);
  }

  /**
   * ่Žทๅ–้ขๅŒ…ๅฑ‘ๆ˜ ๅฐ„
   * @param {Object} menuData ่œๅ•้…็ฝฎ
   */
  getBreadcrumbNameMap() {
    const routerMap = {};
    const mergeMenuAndRouter = data => {
      data.forEach(menuItem => {
        if (menuItem.children) {
          mergeMenuAndRouter(menuItem.children);
        }
        // Reduce memory usage
        routerMap[menuItem.path] = menuItem;
      });
    };
    mergeMenuAndRouter(this.getMenuData());
    return routerMap;
  }

153
  getPageTitle = pathname => {
jim's avatar
jim committed
154 155
    let currRouterData = null;
    // match params path
156
    Object.keys(this.breadcrumbNameMap).forEach(key => {
jim's avatar
jim committed
157
      if (pathToRegexp(key).test(pathname)) {
158
        currRouterData = this.breadcrumbNameMap[key];
jim's avatar
jim committed
159 160
      }
    });
161 162
    if (!currRouterData) {
      return 'Ant Design Pro';
ddcat1115's avatar
ddcat1115 committed
163
    }
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
164
    const message = formatMessage({
165 166 167 168 169
      id: currRouterData.locale || currRouterData.name,
      defaultMessage: currRouterData.name,
    });
    return `${message} - Ant Design Pro`;
  };
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
170

jim's avatar
jim committed
171
  getLayoutStyle = () => {
172 173
    const { fixSiderbar, collapsed, layout } = this.props;
    if (fixSiderbar && layout !== 'topmenu') {
jim's avatar
jim committed
174
      return {
175
        paddingLeft: collapsed ? '80px' : '256px',
jim's avatar
jim committed
176 177 178 179
      };
    }
    return null;
  };
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
180

jim's avatar
jim committed
181 182 183 184 185 186 187
  getContentStyle = () => {
    const { fixedHeader } = this.props;
    return {
      margin: '24px 24px 0',
      paddingTop: fixedHeader ? 64 : 0,
    };
  };
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
188

jim's avatar
jim committed
189
  handleMenuCollapse = collapsed => {
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
190 191
    const { dispatch } = this.props;
    dispatch({
192 193 194
      type: 'global/changeLayoutCollapsed',
      payload: collapsed,
    });
jim's avatar
jim committed
195
  };
196

197
  render() {
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
198
    const {
afc163's avatar
afc163 committed
199
      navTheme,
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
200
      layout: PropsLayout,
ๆ„š้“'s avatar
ๆ„š้“ committed
201
      children,
202
      location: { pathname },
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
203
    } = this.props;
afc163's avatar
afc163 committed
204
    const { rendering, isMobile } = this.state;
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
205
    const isTop = PropsLayout === 'topmenu';
206
    const menuData = this.getMenuData();
afc163's avatar
afc163 committed
207 208
    const layout = (
      <Layout>
jim's avatar
jim committed
209
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
210 211 212
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
afc163's avatar
afc163 committed
213
            theme={navTheme}
jim's avatar
jim committed
214
            onCollapse={this.handleMenuCollapse}
215
            menuData={menuData}
216
            isMobile={isMobile}
jim's avatar
jim committed
217
            {...this.props}
jim's avatar
jim committed
218 219
          />
        )}
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
220 221 222 223 224 225
        <Layout
          style={{
            ...this.getLayoutStyle(),
            minHeight: '100vh',
          }}
        >
226 227 228 229
          <Header
            menuData={menuData}
            handleMenuCollapse={this.handleMenuCollapse}
            logo={logo}
230
            isMobile={isMobile}
231 232
            {...this.props}
          />
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
233
          <Content style={this.getContentStyle()}>{children}</Content>
jim's avatar
jim committed
234
          <Footer />
235
        </Layout>
afc163's avatar
afc163 committed
236 237 238
      </Layout>
    );
    return (
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
239 240 241 242 243 244 245 246 247 248
      <React.Fragment>
        <DocumentTitle title={this.getPageTitle(pathname)}>
          <ContainerQuery query={query}>
            {params => (
              <Context.Provider value={this.getContext()}>
                <div className={classNames(params)}>{layout}</div>
              </Context.Provider>
            )}
          </ContainerQuery>
        </DocumentTitle>
249
        {rendering && process.env.NODE_ENV === 'production' ? null : ( // Do show SettingDrawer in production
afc163's avatar
afc163 committed
250 251
          <SettingDrawer />
        )}
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
252
      </React.Fragment>
253 254 255 256
    );
  }
}

jim's avatar
jim committed
257
export default connect(({ global, setting }) => ({
258
  collapsed: global.collapsed,
jim's avatar
jim committed
259 260
  layout: setting.layout,
  ...setting,
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
261
}))(BasicLayout);