BasicLayout.js 7.12 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

189 190 191 192
  getBashRedirect = () => {
    // According to the url parameter to redirect
    // ่ฟ™้‡Œๆ˜ฏ้‡ๅฎšๅ‘็š„,้‡ๅฎšๅ‘ๅˆฐ url ็š„ redirect ๅ‚ๆ•ฐๆ‰€็คบๅœฐๅ€
    const urlParams = new URL(window.location.href);
jim's avatar
jim committed
193 194

    const redirect = urlParams.searchParams.get('redirect');
195
    // Remove the parameters in the url
jim's avatar
jim committed
196 197 198 199
    if (redirect) {
      urlParams.searchParams.delete('redirect');
      window.history.replaceState(null, 'redirect', urlParams.href);
    } else {
ddcat1115's avatar
ddcat1115 committed
200 201
      const { routerData } = this.props;
      // get the first authorized route path in routerData
jim's avatar
jim committed
202 203 204
      const authorizedPath = Object.keys(routerData).find(
        item => check(routerData[item].authority, item) && item !== '/'
      );
ddcat1115's avatar
ddcat1115 committed
205
      return authorizedPath;
jim's avatar
jim committed
206
    }
207
    return redirect;
jim's avatar
jim committed
208
  };
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
209

jim's avatar
jim committed
210
  handleMenuCollapse = collapsed => {
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
211 212
    const { dispatch } = this.props;
    dispatch({
213 214 215
      type: 'global/changeLayoutCollapsed',
      payload: collapsed,
    });
jim's avatar
jim committed
216
  };
217

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

jim's avatar
jim committed
278
export default connect(({ global, setting }) => ({
279
  collapsed: global.collapsed,
jim's avatar
jim committed
280 281
  layout: setting.layout,
  ...setting,
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
282
}))(BasicLayout);