BasicLayout.js 6.67 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 110 111
  componentDidUpdate(preProps) {
    // After changing to phone mode,
    // if collapsed is true, you need to click twice to display
112
    this.breadcrumbNameMap = this.getBreadcrumbNameMap();
113 114 115 116 117
    const { isMobile } = this.state;
    const { collapsed } = this.props;
    if (isMobile && !preProps.isMobile && !collapsed) {
      this.handleMenuCollapse(false);
    }
118 119 120 121
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.renderRef);
afc163's avatar
afc163 committed
122
    unenquireScreen(this.enquireHandler);
123 124
  }

jim's avatar
jim committed
125
  getContext() {
126
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
127 128
    return {
      location,
129
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
130
    };
131
  }
132

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
  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;
  }

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

jim's avatar
jim committed
177
  getLayoutStyle = () => {
178
    const { isMobile } = this.state;
179
    const { fixSiderbar, collapsed, layout } = this.props;
180
    if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
jim's avatar
jim committed
181
      return {
182
        paddingLeft: collapsed ? '80px' : '256px',
jim's avatar
jim committed
183 184 185 186
      };
    }
    return null;
  };
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
187

jim's avatar
jim committed
188 189 190 191 192 193 194
  getContentStyle = () => {
    const { fixedHeader } = this.props;
    return {
      margin: '24px 24px 0',
      paddingTop: fixedHeader ? 64 : 0,
    };
  };
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
195

jim's avatar
jim committed
196
  handleMenuCollapse = collapsed => {
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
197 198
    const { dispatch } = this.props;
    dispatch({
199 200 201
      type: 'global/changeLayoutCollapsed',
      payload: collapsed,
    });
jim's avatar
jim committed
202
  };
203

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

jim's avatar
jim committed
264
export default connect(({ global, setting }) => ({
265
  collapsed: global.collapsed,
jim's avatar
jim committed
266 267
  layout: setting.layout,
  ...setting,
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
268
}))(BasicLayout);