BasicLayout.js 6.34 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;
ddcat1115's avatar
ddcat1115 committed
21

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

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

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

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

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

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

jim's avatar
jim committed
118
  getContext() {
119
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
120 121
    return {
      location,
122
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
123
    };
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
  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;
  }

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

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

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

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

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

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