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
  const mergeMenuAndRouter = data => {
    data.forEach(menuItem => {
afc163's avatar
afc163 committed
62 63
      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
    this.breadcrumbNameMap = getBreadcrumbNameMap(menuData);
陈帅's avatar
陈帅 committed
104
  }
105 106 107

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

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

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

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

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

陈帅's avatar
陈帅 committed
151
  getPageTitle = pathname => {
jim's avatar
jim committed
152 153
    let currRouterData = null;
    // match params path
陈帅's avatar
陈帅 committed
154
    Object.keys(this.breadcrumbNameMap).forEach(key => {
jim's avatar
jim committed
155
      if (pathToRegexp(key).test(pathname)) {
陈帅's avatar
陈帅 committed
156
        currRouterData = this.breadcrumbNameMap[key];
jim's avatar
jim committed
157 158
      }
    });
陈帅's avatar
陈帅 committed
159 160
    if (!currRouterData) {
      return 'Ant Design Pro';
ddcat1115's avatar
ddcat1115 committed
161
    }
陈帅's avatar
陈帅 committed
162
    const message = formatMessage({
陈帅's avatar
陈帅 committed
163 164 165 166 167
      id: currRouterData.locale || currRouterData.name,
      defaultMessage: currRouterData.name,
    });
    return `${message} - Ant Design Pro`;
  };
陈帅's avatar
陈帅 committed
168

jim's avatar
jim committed
169
  getLayoutStyle = () => {
陈帅's avatar
陈帅 committed
170 171
    const { fixSiderbar, collapsed, layout } = this.props;
    if (fixSiderbar && layout !== 'topmenu') {
jim's avatar
jim committed
172
      return {
173
        paddingLeft: collapsed ? '80px' : '256px',
jim's avatar
jim committed
174 175 176 177
      };
    }
    return null;
  };
陈帅's avatar
陈帅 committed
178

jim's avatar
jim committed
179 180 181 182 183 184 185
  getContentStyle = () => {
    const { fixedHeader } = this.props;
    return {
      margin: '24px 24px 0',
      paddingTop: fixedHeader ? 64 : 0,
    };
  };
陈帅's avatar
陈帅 committed
186

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

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

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

216
  render() {
陈帅's avatar
陈帅 committed
217 218 219
    const {
      silderTheme,
      layout: PropsLayout,
愚道's avatar
愚道 committed
220
      children,
陈帅's avatar
陈帅 committed
221
      location: { pathname },
陈帅's avatar
陈帅 committed
222
    } = this.props;
afc163's avatar
afc163 committed
223
    const { rendering, isMobile } = this.state;
陈帅's avatar
陈帅 committed
224
    const isTop = PropsLayout === 'topmenu';
afc163's avatar
afc163 committed
225 226
    const layout = (
      <Layout>
jim's avatar
jim committed
227
        {isTop && !isMobile ? null : (
jim's avatar
jim committed
228 229 230
          <SiderMenu
            logo={logo}
            Authorized={Authorized}
陈帅's avatar
陈帅 committed
231
            theme={silderTheme}
jim's avatar
jim committed
232
            onCollapse={this.handleMenuCollapse}
afc163's avatar
afc163 committed
233
            menuData={menuData}
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);