BasicLayout.js 6.34 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
  }

  componentDidUpdate() {
110
    this.breadcrumbNameMap = this.getBreadcrumbNameMap();
111 112 113 114
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.renderRef);
afc163's avatar
afc163 committed
115
    unenquireScreen(this.enquireHandler);
116 117
  }

jim's avatar
jim committed
118
  getContext() {
陈帅's avatar
陈帅 committed
119
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
120 121
    return {
      location,
陈帅's avatar
陈帅 committed
122
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
123
    };
124
  }
125

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
  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
152
  getPageTitle = pathname => {
jim's avatar
jim committed
153 154
    let currRouterData = null;
    // match params path
陈帅's avatar
陈帅 committed
155
    Object.keys(this.breadcrumbNameMap).forEach(key => {
jim's avatar
jim committed
156
      if (pathToRegexp(key).test(pathname)) {
陈帅's avatar
陈帅 committed
157
        currRouterData = this.breadcrumbNameMap[key];
jim's avatar
jim committed
158 159
      }
    });
陈帅's avatar
陈帅 committed
160 161
    if (!currRouterData) {
      return 'Ant Design Pro';
ddcat1115's avatar
ddcat1115 committed
162
    }
陈帅's avatar
陈帅 committed
163
    const message = formatMessage({
陈帅's avatar
陈帅 committed
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 = () => {
陈帅's avatar
陈帅 committed
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

jim's avatar
jim committed
188
  handleMenuCollapse = collapsed => {
陈帅's avatar
陈帅 committed
189 190
    const { dispatch } = this.props;
    dispatch({
191 192 193
      type: 'global/changeLayoutCollapsed',
      payload: collapsed,
    });
jim's avatar
jim committed
194
  };
195

196
  render() {
陈帅's avatar
陈帅 committed
197
    const {
afc163's avatar
afc163 committed
198
      navTheme,
陈帅's avatar
陈帅 committed
199
      layout: PropsLayout,
愚道's avatar
愚道 committed
200
      children,
陈帅's avatar
陈帅 committed
201
      location: { pathname },
陈帅's avatar
陈帅 committed
202
    } = this.props;
afc163's avatar
afc163 committed
203
    const { rendering, isMobile } = this.state;
陈帅's avatar
陈帅 committed
204
    const isTop = PropsLayout === 'topmenu';
205
    const menuData = this.getMenuData();
afc163's avatar
afc163 committed
206 207
    const layout = (
      <Layout>
jim's avatar
jim committed
208
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
209 210 211
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
afc163's avatar
afc163 committed
212
            theme={navTheme}
jim's avatar
jim committed
213
            onCollapse={this.handleMenuCollapse}
214
            menuData={menuData}
215
            isMobile={isMobile}
jim's avatar
jim committed
216
            {...this.props}
jim's avatar
jim committed
217 218
          />
        )}
陈帅's avatar
陈帅 committed
219 220 221 222 223 224
        <Layout
          style={{
            ...this.getLayoutStyle(),
            minHeight: '100vh',
          }}
        >
225 226 227 228
          <Header
            menuData={menuData}
            handleMenuCollapse={this.handleMenuCollapse}
            logo={logo}
229
            isMobile={isMobile}
230 231
            {...this.props}
          />
陈帅's avatar
陈帅 committed
232
          <Content style={this.getContentStyle()}>{children}</Content>
jim's avatar
jim committed
233
          <Footer />
234
        </Layout>
afc163's avatar
afc163 committed
235 236 237
      </Layout>
    );
    return (
陈帅's avatar
陈帅 committed
238 239 240 241 242 243 244 245 246 247
      <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>
248
        {rendering && process.env.NODE_ENV === 'production' ? null : ( // Do show SettingDrawer in production
afc163's avatar
afc163 committed
249 250
          <SettingDrawer />
        )}
陈帅's avatar
陈帅 committed
251
      </React.Fragment>
252 253 254 255
    );
  }
}

jim's avatar
jim committed
256
export default connect(({ global, setting }) => ({
Andreas Cederström's avatar
Andreas Cederström committed
257
  collapsed: global.collapsed,
jim's avatar
jim committed
258 259
  layout: setting.layout,
  ...setting,
陈帅's avatar
陈帅 committed
260
}))(BasicLayout);