BasicLayout.js 7.06 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;
陈帅's avatar
陈帅 committed
21
const { check } = Authorized;
ddcat1115's avatar
ddcat1115 committed
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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