BasicLayout.js 6.97 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';
afc163's avatar
afc163 committed
19 20
// TODO: should use this.props.routes
import routerConfig from '../../config/router.config';
21

jim's avatar
jim committed
22
const { Content } = Layout;
陈帅's avatar
陈帅 committed
23
const { check } = Authorized;
ddcat1115's avatar
ddcat1115 committed
24

afc163's avatar
afc163 committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
// 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;
  });
}

// get menu map data
const menuData = formatter(routerConfig[1].routes);

54 55 56 57
/**
 * 获取面包屑映射
 * @param {Object} menuData 菜单配置
 */
afc163's avatar
afc163 committed
58
const getBreadcrumbNameMap = memoizeOne(menu => {
陈帅's avatar
陈帅 committed
59
  const routerMap = {};
afc163's avatar
afc163 committed
60 61 62 63
  const mergeMenuAndRouter = () => {
    menuData.forEach(menuItem => {
      if (menuItem.children) {
        mergeMenuAndRouter(menuItem.children);
陈帅's avatar
陈帅 committed
64
      }
65
      // Reduce memory usage
afc163's avatar
afc163 committed
66
      routerMap[menuItem.path] = menuItem;
陈帅's avatar
陈帅 committed
67 68
    });
  };
afc163's avatar
afc163 committed
69
  mergeMenuAndRouter(menu);
陈帅's avatar
陈帅 committed
70
  return routerMap;
afc163's avatar
afc163 committed
71
}, isEqual);
72

afc163's avatar
afc163 committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
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
91 92 93 94
    maxWidth: 1599,
  },
  'screen-xxl': {
    minWidth: 1600,
afc163's avatar
afc163 committed
95 96 97
  },
};

98
class BasicLayout extends React.PureComponent {
陈帅's avatar
陈帅 committed
99 100
  constructor(props) {
    super(props);
陈帅's avatar
陈帅 committed
101
    this.getPageTitle = memoizeOne(this.getPageTitle);
102
    // Because there are many places to be. So put it here
afc163's avatar
afc163 committed
103 104
    this.breadcrumbNameMap = getBreadcrumbNameMap();
    console.log(this.breadcrumbNameMap);
陈帅's avatar
陈帅 committed
105
  }
106 107 108

  state = {
    rendering: true,
afc163's avatar
afc163 committed
109
    isMobile: false,
110 111 112
  };

  componentDidMount() {
afc163's avatar
afc163 committed
113 114 115 116 117 118 119
    const { dispatch } = this.props;
    dispatch({
      type: 'user/fetchCurrent',
    });
    dispatch({
      type: 'setting/getSetting',
    });
120 121 122 123 124
    this.renderRef = requestAnimationFrame(() => {
      this.setState({
        rendering: false,
      });
    });
afc163's avatar
afc163 committed
125 126 127 128 129 130 131 132
    this.enquireHandler = enquireScreen(mobile => {
      const { isMobile } = this.state;
      if (isMobile !== mobile) {
        this.setState({
          isMobile: mobile,
        });
      }
    });
133 134 135
  }

  componentDidUpdate() {
afc163's avatar
afc163 committed
136
    this.breadcrumbNameMap = getBreadcrumbNameMap();
137 138 139 140
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.renderRef);
afc163's avatar
afc163 committed
141
    unenquireScreen(this.enquireHandler);
142 143
  }

jim's avatar
jim committed
144
  getContext() {
陈帅's avatar
陈帅 committed
145
    const { location } = this.props;
ddcat1115's avatar
ddcat1115 committed
146 147
    return {
      location,
陈帅's avatar
陈帅 committed
148
      breadcrumbNameMap: this.breadcrumbNameMap,
ddcat1115's avatar
ddcat1115 committed
149
    };
150
  }
151

陈帅'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

188 189 190 191
  getBashRedirect = () => {
    // According to the url parameter to redirect
    // 这里是重定向的,重定向到 url 的 redirect 参数所示地址
    const urlParams = new URL(window.location.href);
jim's avatar
jim committed
192 193

    const redirect = urlParams.searchParams.get('redirect');
194
    // Remove the parameters in the url
jim's avatar
jim committed
195 196 197 198
    if (redirect) {
      urlParams.searchParams.delete('redirect');
      window.history.replaceState(null, 'redirect', urlParams.href);
    } else {
ddcat1115's avatar
ddcat1115 committed
199 200
      const { routerData } = this.props;
      // get the first authorized route path in routerData
jim's avatar
jim committed
201 202 203
      const authorizedPath = Object.keys(routerData).find(
        item => check(routerData[item].authority, item) && item !== '/'
      );
ddcat1115's avatar
ddcat1115 committed
204
      return authorizedPath;
jim's avatar
jim committed
205
    }
206
    return redirect;
jim's avatar
jim committed
207
  };
陈帅's avatar
陈帅 committed
208

jim's avatar
jim committed
209
  handleMenuCollapse = collapsed => {
陈帅's avatar
陈帅 committed
210 211
    const { dispatch } = this.props;
    dispatch({
212 213 214
      type: 'global/changeLayoutCollapsed',
      payload: collapsed,
    });
jim's avatar
jim committed
215
  };
216

217
  render() {
陈帅's avatar
陈帅 committed
218 219 220
    const {
      silderTheme,
      layout: PropsLayout,
愚道's avatar
愚道 committed
221
      children,
陈帅's avatar
陈帅 committed
222
      location: { pathname },
陈帅's avatar
陈帅 committed
223
    } = this.props;
afc163's avatar
afc163 committed
224
    const { rendering, isMobile } = this.state;
陈帅's avatar
陈帅 committed
225
    const isTop = PropsLayout === 'topmenu';
afc163's avatar
afc163 committed
226 227
    const layout = (
      <Layout>
jim's avatar
jim committed
228
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
229 230 231
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
陈帅's avatar
陈帅 committed
232
            theme={silderTheme}
jim's avatar
jim committed
233
            onCollapse={this.handleMenuCollapse}
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',
          }}
        >
jim's avatar
jim committed
243
          <Header handleMenuCollapse={this.handleMenuCollapse} logo={logo} {...this.props} />
陈帅's avatar
陈帅 committed
244
          <Content style={this.getContentStyle()}>{children}</Content>
jim's avatar
jim committed
245
          <Footer />
246
        </Layout>
afc163's avatar
afc163 committed
247 248 249
      </Layout>
    );
    return (
陈帅's avatar
陈帅 committed
250 251 252 253 254 255 256 257 258 259
      <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>
260
        {rendering && process.env.NODE_ENV === 'production' ? null : ( // Do show SettingDrawer in production
afc163's avatar
afc163 committed
261 262
          <SettingDrawer />
        )}
陈帅's avatar
陈帅 committed
263
      </React.Fragment>
264 265 266 267
    );
  }
}

jim's avatar
jim committed
268
export default connect(({ global, setting }) => ({
Andreas Cederström's avatar
Andreas Cederström committed
269
  collapsed: global.collapsed,
jim's avatar
jim committed
270 271
  layout: setting.layout,
  ...setting,
陈帅's avatar
陈帅 committed
272
}))(BasicLayout);