BasicLayout.js 6.97 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';
afc163's avatar
afc163 committed
19 20
// TODO: should use this.props.routes
import routerConfig from '../../config/router.config';
21

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

afc163's avatar
afc163 committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
// 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;
  });
}

// get menu map data
const menuData = formatter(routerConfig[1].routes);

54 55 56 57
/**
 * ่Žทๅ–้ขๅŒ…ๅฑ‘ๆ˜ ๅฐ„
 * @param {Object} menuData ่œๅ•้…็ฝฎ
 */
afc163's avatar
afc163 committed
58
const getBreadcrumbNameMap = memoizeOne(menu => {
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
59
  const routerMap = {};
afc163's avatar
afc163 committed
60 61 62 63
  const mergeMenuAndRouter = () => {
    menuData.forEach(menuItem => {
      if (menuItem.children) {
        mergeMenuAndRouter(menuItem.children);
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
64
      }
65
      // Reduce memory usage
afc163's avatar
afc163 committed
66
      routerMap[menuItem.path] = menuItem;
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
67 68
    });
  };
afc163's avatar
afc163 committed
69
  mergeMenuAndRouter(menu);
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
70
  return routerMap;
afc163's avatar
afc163 committed
71
}, isEqual);
72

afc163's avatar
afc163 committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
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
91 92 93 94
    maxWidth: 1599,
  },
  'screen-xxl': {
    minWidth: 1600,
afc163's avatar
afc163 committed
95 96 97
  },
};

98
class BasicLayout extends React.PureComponent {
99 100
  constructor(props) {
    super(props);
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
101
    this.getPageTitle = memoizeOne(this.getPageTitle);
102
    // Because there are many places to be. So put it here
afc163's avatar
afc163 committed
103 104
    this.breadcrumbNameMap = getBreadcrumbNameMap();
    console.log(this.breadcrumbNameMap);
105
  }
106 107 108

  state = {
    rendering: true,
afc163's avatar
afc163 committed
109
    isMobile: false,
110 111 112
  };

  componentDidMount() {
afc163's avatar
afc163 committed
113 114 115 116 117 118 119
    const { dispatch } = this.props;
    dispatch({
      type: 'user/fetchCurrent',
    });
    dispatch({
      type: 'setting/getSetting',
    });
120 121 122 123 124
    this.renderRef = requestAnimationFrame(() => {
      this.setState({
        rendering: false,
      });
    });
afc163's avatar
afc163 committed
125 126 127 128 129 130 131 132
    this.enquireHandler = enquireScreen(mobile => {
      const { isMobile } = this.state;
      if (isMobile !== mobile) {
        this.setState({
          isMobile: mobile,
        });
      }
    });
133 134 135
  }

  componentDidUpdate() {
afc163's avatar
afc163 committed
136
    this.breadcrumbNameMap = getBreadcrumbNameMap();
137 138 139 140
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.renderRef);
afc163's avatar
afc163 committed
141
    unenquireScreen(this.enquireHandler);
142 143
  }

jim's avatar
jim committed
144
  getContext() {
145
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
146 147
    return {
      location,
148
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
149
    };
150
  }
151

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

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

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

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

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

jim's avatar
jim committed
268
export default connect(({ global, setting }) => ({
269
  collapsed: global.collapsed,
jim's avatar
jim committed
270 271
  layout: setting.layout,
  ...setting,
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
272
}))(BasicLayout);