BasicLayout.js 6.86 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';
19

jim's avatar
jim committed
20
const { Content } = Layout;
ddcat1115's avatar
ddcat1115 committed
21

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

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

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

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

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

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

jim's avatar
jim committed
125
  getContext() {
陈帅's avatar
陈帅 committed
126
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
127 128
    return {
      location,
陈帅's avatar
陈帅 committed
129
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
130
    };
131
  }
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
  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
159
  getPageTitle = pathname => {
jim's avatar
jim committed
160 161
    let currRouterData = null;
    // match params path
陈帅's avatar
陈帅 committed
162
    Object.keys(this.breadcrumbNameMap).forEach(key => {
jim's avatar
jim committed
163
      if (pathToRegexp(key).test(pathname)) {
陈帅's avatar
陈帅 committed
164
        currRouterData = this.breadcrumbNameMap[key];
jim's avatar
jim committed
165 166
      }
    });
陈帅's avatar
陈帅 committed
167 168
    if (!currRouterData) {
      return 'Ant Design Pro';
ddcat1115's avatar
ddcat1115 committed
169
    }
陈帅's avatar
陈帅 committed
170
    const message = formatMessage({
陈帅's avatar
陈帅 committed
171 172 173 174 175
      id: currRouterData.locale || currRouterData.name,
      defaultMessage: currRouterData.name,
    });
    return `${message} - Ant Design Pro`;
  };
陈帅's avatar
陈帅 committed
176

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

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

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

204 205 206 207 208 209 210 211 212 213
  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 />;
  }

214
  render() {
陈帅's avatar
陈帅 committed
215
    const {
afc163's avatar
afc163 committed
216
      navTheme,
陈帅's avatar
陈帅 committed
217
      layout: PropsLayout,
愚道's avatar
愚道 committed
218
      children,
陈帅's avatar
陈帅 committed
219
      location: { pathname },
陈帅's avatar
陈帅 committed
220
    } = this.props;
221
    const { isMobile } = this.state;
陈帅's avatar
陈帅 committed
222
    const isTop = PropsLayout === 'topmenu';
223
    const menuData = this.getMenuData();
afc163's avatar
afc163 committed
224 225
    const layout = (
      <Layout>
jim's avatar
jim committed
226
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
227 228 229
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
afc163's avatar
afc163 committed
230
            theme={navTheme}
jim's avatar
jim committed
231
            onCollapse={this.handleMenuCollapse}
232
            menuData={menuData}
233
            isMobile={isMobile}
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',
          }}
        >
243 244 245 246
          <Header
            menuData={menuData}
            handleMenuCollapse={this.handleMenuCollapse}
            logo={logo}
247
            isMobile={isMobile}
248 249
            {...this.props}
          />
陈帅's avatar
陈帅 committed
250
          <Content style={this.getContentStyle()}>{children}</Content>
jim's avatar
jim committed
251
          <Footer />
252
        </Layout>
afc163's avatar
afc163 committed
253 254
      </Layout>
    );
255

afc163's avatar
afc163 committed
256
    return (
陈帅's avatar
陈帅 committed
257 258 259 260 261 262 263 264 265 266
      <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>
267
        {this.renderSettingDrawer()}
陈帅's avatar
陈帅 committed
268
      </React.Fragment>
269 270 271 272
    );
  }
}

jim's avatar
jim committed
273
export default connect(({ global, setting }) => ({
Andreas Cederström's avatar
Andreas Cederström committed
274
  collapsed: global.collapsed,
jim's avatar
jim committed
275 276
  layout: setting.layout,
  ...setting,
陈帅's avatar
陈帅 committed
277
}))(BasicLayout);