BasicLayout.js 7.27 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';
陈帅's avatar
陈帅 committed
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';
陈帅's avatar
陈帅 committed
19
import Exception403 from '../pages/Exception/403';
20

jim's avatar
jim committed
21
const { Content } = Layout;
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;
  });
}

陈帅's avatar
陈帅 committed
49 50
const memoizeOneFormatter = memoizeOne(formatter, isEqual);

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

76
class BasicLayout extends React.PureComponent {
陈帅's avatar
陈帅 committed
77 78
  constructor(props) {
    super(props);
陈帅's avatar
陈帅 committed
79
    this.getPageTitle = memoizeOne(this.getPageTitle);
80 81
    this.getBreadcrumbNameMap = memoizeOne(this.getBreadcrumbNameMap, isEqual);
    this.breadcrumbNameMap = this.getBreadcrumbNameMap();
陈帅's avatar
陈帅 committed
82
    this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
陈帅's avatar
陈帅 committed
83
  }
84 85 86

  state = {
    rendering: true,
afc163's avatar
afc163 committed
87
    isMobile: false,
88 89 90
  };

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

113 114 115
  componentDidUpdate(preProps) {
    // After changing to phone mode,
    // if collapsed is true, you need to click twice to display
116
    this.breadcrumbNameMap = this.getBreadcrumbNameMap();
117 118 119 120 121
    const { isMobile } = this.state;
    const { collapsed } = this.props;
    if (isMobile && !preProps.isMobile && !collapsed) {
      this.handleMenuCollapse(false);
    }
122 123 124 125
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.renderRef);
afc163's avatar
afc163 committed
126
    unenquireScreen(this.enquireHandler);
127 128
  }

jim's avatar
jim committed
129
  getContext() {
陈帅's avatar
陈帅 committed
130
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
131 132
    return {
      location,
陈帅's avatar
陈帅 committed
133
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
134
    };
135
  }
136

137 138 139 140
  getMenuData() {
    const {
      route: { routes },
    } = this.props;
陈帅's avatar
陈帅 committed
141
    return memoizeOneFormatter(routes);
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
  }

  /**
   * 获取面包屑映射
   * @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;
  }

陈帅's avatar
陈帅 committed
163 164 165 166 167 168 169
  matchParamsPath = pathname => {
    const pathKey = Object.keys(this.breadcrumbNameMap).find(key =>
      pathToRegexp(key).test(pathname)
    );
    return this.breadcrumbNameMap[pathKey];
  };

陈帅's avatar
陈帅 committed
170
  getPageTitle = pathname => {
陈帅's avatar
陈帅 committed
171 172
    const currRouterData = this.matchParamsPath(pathname);

陈帅's avatar
陈帅 committed
173 174
    if (!currRouterData) {
      return 'Ant Design Pro';
ddcat1115's avatar
ddcat1115 committed
175
    }
陈帅's avatar
陈帅 committed
176
    const message = formatMessage({
陈帅's avatar
陈帅 committed
177 178 179 180 181
      id: currRouterData.locale || currRouterData.name,
      defaultMessage: currRouterData.name,
    });
    return `${message} - Ant Design Pro`;
  };
陈帅's avatar
陈帅 committed
182

jim's avatar
jim committed
183
  getLayoutStyle = () => {
184
    const { isMobile } = this.state;
陈帅's avatar
陈帅 committed
185
    const { fixSiderbar, collapsed, layout } = this.props;
186
    if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
jim's avatar
jim committed
187
      return {
188
        paddingLeft: collapsed ? '80px' : '256px',
jim's avatar
jim committed
189 190 191 192
      };
    }
    return null;
  };
陈帅's avatar
陈帅 committed
193

jim's avatar
jim committed
194 195 196 197 198 199 200
  getContentStyle = () => {
    const { fixedHeader } = this.props;
    return {
      margin: '24px 24px 0',
      paddingTop: fixedHeader ? 64 : 0,
    };
  };
陈帅's avatar
陈帅 committed
201

jim's avatar
jim committed
202
  handleMenuCollapse = collapsed => {
陈帅's avatar
陈帅 committed
203 204
    const { dispatch } = this.props;
    dispatch({
205 206 207
      type: 'global/changeLayoutCollapsed',
      payload: collapsed,
    });
jim's avatar
jim committed
208
  };
209

210
  renderSettingDrawer() {
kennylbj's avatar
kennylbj committed
211 212
    // Do not render SettingDrawer in production
    // unless it is deployed in preview.pro.ant.design as demo
213 214 215 216 217 218 219
    const { rendering } = this.state;
    if ((rendering || process.env.NODE_ENV === 'production') && APP_TYPE !== 'site') {
      return null;
    }
    return <SettingDrawer />;
  }

220
  render() {
陈帅's avatar
陈帅 committed
221
    const {
afc163's avatar
afc163 committed
222
      navTheme,
陈帅's avatar
陈帅 committed
223
      layout: PropsLayout,
愚道's avatar
愚道 committed
224
      children,
陈帅's avatar
陈帅 committed
225
      location: { pathname },
陈帅's avatar
陈帅 committed
226
    } = this.props;
227
    const { isMobile } = this.state;
陈帅's avatar
陈帅 committed
228
    const isTop = PropsLayout === 'topmenu';
229
    const menuData = this.getMenuData();
陈帅's avatar
陈帅 committed
230
    const routerConfig = this.matchParamsPath(pathname);
afc163's avatar
afc163 committed
231 232
    const layout = (
      <Layout>
jim's avatar
jim committed
233
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
234 235 236
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
afc163's avatar
afc163 committed
237
            theme={navTheme}
jim's avatar
jim committed
238
            onCollapse={this.handleMenuCollapse}
239
            menuData={menuData}
240
            isMobile={isMobile}
jim's avatar
jim committed
241
            {...this.props}
jim's avatar
jim committed
242 243
          />
        )}
陈帅's avatar
陈帅 committed
244 245 246 247 248 249
        <Layout
          style={{
            ...this.getLayoutStyle(),
            minHeight: '100vh',
          }}
        >
250 251 252 253
          <Header
            menuData={menuData}
            handleMenuCollapse={this.handleMenuCollapse}
            logo={logo}
254
            isMobile={isMobile}
255 256
            {...this.props}
          />
陈帅's avatar
陈帅 committed
257 258 259 260 261
          <Content style={this.getContentStyle()}>
            <Authorized authority={routerConfig.authority} noMatch={<Exception403 />}>
              {children}
            </Authorized>
          </Content>
jim's avatar
jim committed
262
          <Footer />
263
        </Layout>
afc163's avatar
afc163 committed
264 265 266
      </Layout>
    );
    return (
陈帅's avatar
陈帅 committed
267 268 269 270 271 272 273 274 275 276
      <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>
277
        {this.renderSettingDrawer()}
陈帅's avatar
陈帅 committed
278
      </React.Fragment>
279 280 281 282
    );
  }
}

jim's avatar
jim committed
283
export default connect(({ global, setting }) => ({
Andreas Cederström's avatar
Andreas Cederström committed
284
  collapsed: global.collapsed,
jim's avatar
jim committed
285 286
  layout: setting.layout,
  ...setting,
陈帅's avatar
陈帅 committed
287
}))(BasicLayout);