BasicLayout.js 7.19 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;
  });
}

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 {
陈帅's avatar
陈帅 committed
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();
陈帅's avatar
陈帅 committed
80
    this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
陈帅's avatar
陈帅 committed
81
  }
82 83 84

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

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

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

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

jim's avatar
jim committed
127
  getContext() {
陈帅's avatar
陈帅 committed
128
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
129 130
    return {
      location,
陈帅's avatar
陈帅 committed
131
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
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 159 160
  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;
  }

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

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

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

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

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

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

208 209 210 211 212 213 214 215 216 217
  renderSettingDrawer() {
    // Do show SettingDrawer in production
    // unless deployed in preview.pro.ant.design as demo
    const { rendering } = this.state;
    if ((rendering || process.env.NODE_ENV === 'production') && APP_TYPE !== 'site') {
      return null;
    }
    return <SettingDrawer />;
  }

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

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