BasicLayout.js 6.62 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 = () => {
陈帅's avatar
陈帅 committed
178 179
    const { fixSiderbar, collapsed, layout } = this.props;
    if (fixSiderbar && layout !== 'topmenu') {
jim's avatar
jim committed
180
      return {
181
        paddingLeft: collapsed ? '80px' : '256px',
jim's avatar
jim committed
182 183 184 185
      };
    }
    return null;
  };
陈帅's avatar
陈帅 committed
186

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

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

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

jim's avatar
jim committed
263
export default connect(({ global, setting }) => ({
Andreas Cederström's avatar
Andreas Cederström committed
264
  collapsed: global.collapsed,
jim's avatar
jim committed
265 266
  layout: setting.layout,
  ...setting,
陈帅's avatar
陈帅 committed
267
}))(BasicLayout);