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
  const mergeMenuAndRouter = data => {
    data.forEach(menuItem => {
afc163's avatar
afc163 committed
62 63
      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
    this.breadcrumbNameMap = getBreadcrumbNameMap(menuData);
104
  }
105 106 107

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

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

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

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

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

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

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

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

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

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

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

216
  render() {
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
217 218 219
    const {
      silderTheme,
      layout: PropsLayout,
ๆ„š้“'s avatar
ๆ„š้“ committed
220
      children,
221
      location: { pathname },
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
222
    } = this.props;
afc163's avatar
afc163 committed
223
    const { rendering, isMobile } = this.state;
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
224
    const isTop = PropsLayout === 'topmenu';
afc163's avatar
afc163 committed
225 226
    const layout = (
      <Layout>
jim's avatar
jim committed
227
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
228 229 230
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
้™ˆๅธ…'s avatar
้™ˆๅธ… committed
231
            theme={silderTheme}
jim's avatar
jim committed
232
            onCollapse={this.handleMenuCollapse}
afc163's avatar
afc163 committed
233
            menuData={menuData}
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);