Просмотр исходного кода

use antd page-header (#3881)

* use antd page-header

* remove page-header

* up ant version

* up antd version

* up antd verison

* use umi-request (#3883)

* use umi-request

* use data repalce to body

* 401 不需要提示请求错误。

* use netlify functions (#3882)

* use netlify functions

* rm .gitignore key

* fix build error

* use new folder

* change config

* add redirects

* add redirects

* add router

* remobe "/"

* remove code

* add tag

* remove babel loader

* rm firebase config

* remove babel

* bugfix: fix#3891 ,dll config error

* add function build

* fix error script

* feat: win add test script (#3845)

* feat: win add test script

* bugfix: fix test error

* use new config

* style: change title size

* style: change title size
陈帅 6 лет назад
Родитель
Сommit
7679507bff

+ 0 - 6
src/components/PageHeader/breadcrumb.d.ts

@@ -1,6 +0,0 @@
-import React from 'react';
-import { PageHeaderProps } from './index';
-
-export default class BreadcrumbView extends React.Component<PageHeaderProps, any> {}
-
-export function getBreadcrumb(breadcrumbNameMap: object, url: string): object;

+ 0 - 178
src/components/PageHeader/breadcrumb.js

@@ -1,178 +0,0 @@
-import React, { PureComponent, createElement } from 'react';
-import pathToRegexp from 'path-to-regexp';
-import { Breadcrumb } from 'antd';
-import styles from './index.less';
-import { urlToList } from '../_utils/pathTools';
-
-export const getBreadcrumb = (breadcrumbNameMap, url) => {
-  let breadcrumb = breadcrumbNameMap[url];
-  if (!breadcrumb) {
-    Object.keys(breadcrumbNameMap).forEach(item => {
-      if (pathToRegexp(item).test(url)) {
-        breadcrumb = breadcrumbNameMap[item];
-      }
-    });
-  }
-  return breadcrumb || {};
-};
-
-export default class BreadcrumbView extends PureComponent {
-  state = {
-    breadcrumb: null,
-  };
-
-  componentDidMount() {
-    this.getBreadcrumbDom();
-  }
-
-  componentDidUpdate(preProps) {
-    const { location } = this.props;
-    if (!location || !preProps.location) {
-      return;
-    }
-    const prePathname = preProps.location.pathname;
-    if (prePathname !== location.pathname) {
-      this.getBreadcrumbDom();
-    }
-  }
-
-  getBreadcrumbDom = () => {
-    const breadcrumb = this.conversionBreadcrumbList();
-    this.setState({
-      breadcrumb,
-    });
-  };
-
-  getBreadcrumbProps = () => {
-    const { routes, params, location, breadcrumbNameMap } = this.props;
-    return {
-      routes,
-      params,
-      routerLocation: location,
-      breadcrumbNameMap,
-    };
-  };
-
-  // Generated according to props
-  conversionFromProps = () => {
-    const { breadcrumbList, breadcrumbSeparator, itemRender, linkElement = 'a' } = this.props;
-    return (
-      <Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
-        {breadcrumbList.map(item => {
-          const title = itemRender ? itemRender(item) : item.title;
-          return (
-            <Breadcrumb.Item key={item.title}>
-              {item.href
-                ? createElement(
-                    linkElement,
-                    {
-                      [linkElement === 'a' ? 'href' : 'to']: item.href,
-                    },
-                    title
-                  )
-                : title}
-            </Breadcrumb.Item>
-          );
-        })}
-      </Breadcrumb>
-    );
-  };
-
-  conversionFromLocation = (routerLocation, breadcrumbNameMap) => {
-    const { breadcrumbSeparator, home, itemRender, linkElement = 'a' } = this.props;
-    // Convert the url to an array
-    const pathSnippets = urlToList(routerLocation.pathname);
-    // Loop data mosaic routing
-    const extraBreadcrumbItems = pathSnippets.map((url, index) => {
-      const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
-      if (currentBreadcrumb.inherited) {
-        return null;
-      }
-      const isLinkable = index !== pathSnippets.length - 1 && currentBreadcrumb.component;
-      const name = itemRender ? itemRender(currentBreadcrumb) : currentBreadcrumb.name;
-      return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
-        <Breadcrumb.Item key={url}>
-          {createElement(
-            isLinkable ? linkElement : 'span',
-            { [linkElement === 'a' ? 'href' : 'to']: url },
-            name
-          )}
-        </Breadcrumb.Item>
-      ) : null;
-    });
-    // Add home breadcrumbs to your head if defined
-    if (home) {
-      extraBreadcrumbItems.unshift(
-        <Breadcrumb.Item key="home">
-          {createElement(
-            linkElement,
-            {
-              [linkElement === 'a' ? 'href' : 'to']: '/',
-            },
-            home
-          )}
-        </Breadcrumb.Item>
-      );
-    }
-    return (
-      <Breadcrumb className={styles.breadcrumb} separator={breadcrumbSeparator}>
-        {extraBreadcrumbItems}
-      </Breadcrumb>
-    );
-  };
-
-  /**
-   * 将参数转化为面包屑
-   * Convert parameters into breadcrumbs
-   */
-  conversionBreadcrumbList = () => {
-    const { breadcrumbList, breadcrumbSeparator } = this.props;
-    const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps();
-    if (breadcrumbList && breadcrumbList.length) {
-      return this.conversionFromProps();
-    }
-    // 如果传入 routes 和 params 属性
-    // If pass routes and params attributes
-    if (routes && params) {
-      return (
-        <Breadcrumb
-          className={styles.breadcrumb}
-          routes={routes.filter(route => route.breadcrumbName)}
-          params={params}
-          itemRender={this.itemRender}
-          separator={breadcrumbSeparator}
-        />
-      );
-    }
-    // 根据 location 生成 面包屑
-    // Generate breadcrumbs based on location
-    if (routerLocation && routerLocation.pathname) {
-      return this.conversionFromLocation(routerLocation, breadcrumbNameMap);
-    }
-    return null;
-  };
-
-  // 渲染Breadcrumb 子节点
-  // Render the Breadcrumb child node
-  itemRender = (route, params, routes, paths) => {
-    const { linkElement = 'a' } = this.props;
-    const last = routes.indexOf(route) === routes.length - 1;
-    return last || !route.component ? (
-      <span>{route.breadcrumbName}</span>
-    ) : (
-      createElement(
-        linkElement,
-        {
-          href: paths.join('/') || '/',
-          to: paths.join('/') || '/',
-        },
-        route.breadcrumbName
-      )
-    );
-  };
-
-  render() {
-    const { breadcrumb } = this.state;
-    return breadcrumb;
-  }
-}

+ 0 - 75
src/components/PageHeader/demo/image.md

@@ -1,75 +0,0 @@
----
-order: 2
-title: With Image
----
-
-带图片的页头。
-
-````jsx
-import PageHeader from 'ant-design-pro/lib/PageHeader';
-
-const content = (
-  <div>
-    <p>段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。</p>
-    <div className="link">
-      <a>
-        <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/MjEImQtenlyueSmVEfUD.svg" /> 快速开始
-      </a>
-      <a>
-        <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/NbuDUAuBlIApFuDvWiND.svg" /> 产品简介
-      </a>
-      <a>
-        <img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/ohOEPSYdDTNnyMbGuyLb.svg" /> 产品文档
-      </a>
-    </div>
-  </div>
-);
-
-const extra = (
-  <div className="imgContainer">
-    <img style={{ width: '100%' }} alt="" src="https://gw.alipayobjects.com/zos/rmsportal/RzwpdLnhmvDJToTdfDPe.png" />
-  </div>
-);
-
-const breadcrumbList = [{
-  title: '一级菜单',
-  href: '/',
-}, {
-  title: '二级菜单',
-  href: '/',
-}, {
-  title: '三级菜单',
-}];
-
-ReactDOM.render(
-  <div>
-    <PageHeader
-      title="这是一个标题"
-      content={content}
-      extraContent={extra}
-      breadcrumbList={breadcrumbList}
-    />
-  </div>
-, mountNode);
-````
-
-<style>
-#scaffold-src-components-PageHeader-demo-image .code-box-demo {
-  background: #f2f4f5;
-}
-#scaffold-src-components-PageHeader-demo-image .imgContainer {
-  margin-top: -60px;
-  text-align: center;
-  width: 195px;
-}
-#scaffold-src-components-PageHeader-demo-image .link {
-	margin-top: 16px;
-}
-#scaffold-src-components-PageHeader-demo-image .link a {
-  margin-right: 32px;
-}
-#scaffold-src-components-PageHeader-demo-image .link img {
-  vertical-align: middle;
-  margin-right: 8px;
-}
-</style>

+ 0 - 32
src/components/PageHeader/demo/simple.md

@@ -1,32 +0,0 @@
----
-order: 3
-title: Simple
----
-
-简单的页头。
-
-````jsx
-import PageHeader from 'ant-design-pro/lib/PageHeader';
-
-const breadcrumbList = [{
-  title: '一级菜单',
-  href: '/',
-}, {
-  title: '二级菜单',
-  href: '/',
-}, {
-  title: '三级菜单',
-}];
-
-ReactDOM.render(
-  <div>
-    <PageHeader title="页面标题" breadcrumbList={breadcrumbList} />
-  </div>
-, mountNode);
-````
-
-<style>
-#scaffold-src-components-PageHeader-demo-simple .code-box-demo {
-  background: #f2f4f5;
-}
-</style>

+ 0 - 102
src/components/PageHeader/demo/standard.md

@@ -1,102 +0,0 @@
----
-order: 1
-title: Standard
----
-
-标准页头。
-
-````jsx
-import PageHeader from 'ant-design-pro/lib/PageHeader';
-import DescriptionList from 'ant-design-pro/lib/DescriptionList';
-import { Button, Menu, Dropdown, Icon, Row, Col } from 'antd';
-
-const { Description } = DescriptionList;
-const ButtonGroup = Button.Group;
-
-const description = (
-  <DescriptionList size="small" col="2">
-    <Description term="创建人">曲丽丽</Description>
-    <Description term="订购产品">XX 服务</Description>
-    <Description term="创建时间">2017-07-07</Description>
-    <Description term="关联单据"><a href="">12421</a></Description>
-  </DescriptionList>
-);
-
-const menu = (
-  <Menu>
-    <Menu.Item key="1">选项一</Menu.Item>
-    <Menu.Item key="2">选项二</Menu.Item>
-    <Menu.Item key="3">选项三</Menu.Item>
-  </Menu>
-);
-
-const action = (
-  <div>
-    <ButtonGroup>
-      <Button>操作</Button>
-      <Button>操作</Button>
-      <Dropdown overlay={menu} placement="bottomRight">
-        <Button><Icon type="ellipsis" /></Button>
-      </Dropdown>
-    </ButtonGroup>
-    <Button type="primary">主操作</Button>
-  </div>
-);
-
-const extra = (
-  <Row>
-    <Col sm={24} md={12}>
-      <div style={{ color: 'rgba(0, 0, 0, 0.43)' }}>状态</div>
-      <div style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 20 }}>待审批</div>
-    </Col>
-    <Col sm={24} md={12}>
-      <div style={{ color: 'rgba(0, 0, 0, 0.43)' }}>订单金额</div>
-      <div style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 20 }}>¥ 568.08</div>
-    </Col>
-  </Row>
-);
-
-const breadcrumbList = [{
-  title: '一级菜单',
-  href: '/',
-}, {
-  title: '二级菜单',
-  href: '/',
-}, {
-  title: '三级菜单',
-}];
-
-const tabList = [{
-  key: 'detail',
-  tab: '详情',
-}, {
-  key: 'rule',
-  tab: '规则',
-}];
-
-function onTabChange(key) {
-  console.log(key);
-}
-
-ReactDOM.render(
-  <div>
-    <PageHeader
-      title="单号:234231029431"
-      logo={<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png" />}
-      action={action}
-      content={description}
-      extraContent={extra}
-      breadcrumbList={breadcrumbList}
-      tabList={tabList}
-      tabActiveKey="detail"
-      onTabChange={onTabChange}
-    />
-  </div>
-, mountNode);
-````
-
-<style>
-#scaffold-src-components-PageHeader-demo-standard .code-box-demo {
-  background: #f2f4f5;
-}
-</style>

+ 0 - 68
src/components/PageHeader/demo/structure.md

@@ -1,68 +0,0 @@
----
-order: 0
-title: Structure
----
-
-基本结构,具备响应式布局功能,主要断点为 768px 和 576px,拖动窗口改变大小试试看。
-
-````jsx
-import PageHeader from 'ant-design-pro/lib/PageHeader';
-
-const breadcrumbList = [{
-  title: '面包屑',
-}];
-
-const tabList = [{
-  key: '1',
-  tab: '页签一',
-}, {
-  key: '2',
-  tab: '页签二',
-}, {
-  key: '3',
-  tab: '页签三',
-}];
-
-ReactDOM.render(
-  <div>
-    <PageHeader
-      className="tabs"
-      title={<div className="title">Title</div>}
-      logo={<div className="logo">logo</div>}
-      action={<div className="action">action</div>}
-      content={<div className="content">content</div>}
-      extraContent={<div className="extraContent">extraContent</div>}
-      breadcrumbList={breadcrumbList}
-      tabList={tabList}
-      tabActiveKey="1"
-    />
-  </div>
-, mountNode);
-````
-
-<style>
-#scaffold-src-components-PageHeader-demo-structure .code-box-demo {
-  background: #f2f4f5;
-}
-#scaffold-src-components-PageHeader-demo-structure .logo {
-  background: #3ba0e9;
-  color: #fff;
-  height: 100%;
-}
-#scaffold-src-components-PageHeader-demo-structure .title {
-  background: rgba(16, 142, 233, 1);
-  color: #fff;
-}
-#scaffold-src-components-PageHeader-demo-structure .action {
-  background: #7dbcea;
-  color: #fff;
-}
-#scaffold-src-components-PageHeader-demo-structure .content {
-  background: #7dbcea;
-  color: #fff;
-}
-#scaffold-src-components-PageHeader-demo-structure .extraContent {
-  background: #7dbcea;
-  color: #fff;
-}
-</style>

+ 0 - 31
src/components/PageHeader/index.d.ts

@@ -1,31 +0,0 @@
-import React from 'react';
-import { Location } from 'history';
-
-export interface PageHeaderProps {
-  title?: React.ReactNode | string | number;
-  logo?: React.ReactNode | string;
-  action?: React.ReactNode | string;
-  content?: React.ReactNode;
-  extraContent?: React.ReactNode;
-  routes?: any[];
-  params?: any;
-  breadcrumbList?: Array<{ title: string | number; href?: string }>;
-  tabList?: Array<{ key: string; tab: React.ReactNode }>;
-  tabActiveKey?: string;
-  tabDefaultActiveKey?: string;
-  onTabChange?: (key: string) => void;
-  tabBarExtraContent?: React.ReactNode;
-  linkElement?: React.ReactNode | string;
-  style?: React.CSSProperties;
-  home?: React.ReactNode;
-  wide?: boolean;
-  hiddenBreadcrumb?: boolean;
-  className?: string;
-  loading?: boolean;
-  breadcrumbSeparator?: React.ReactNode;
-  location?: Location;
-  itemRender: (menuItem: any) => React.ReactNode;
-  breadcrumbNameMap?: any;
-}
-
-export default class PageHeader extends React.Component<PageHeaderProps, any> {}

+ 0 - 82
src/components/PageHeader/index.js

@@ -1,82 +0,0 @@
-import React, { PureComponent } from 'react';
-import { Tabs, Skeleton } from 'antd';
-import classNames from 'classnames';
-import styles from './index.less';
-import BreadcrumbView from './breadcrumb';
-
-const { TabPane } = Tabs;
-export default class PageHeader extends PureComponent {
-  onChange = key => {
-    const { onTabChange } = this.props;
-    if (onTabChange) {
-      onTabChange(key);
-    }
-  };
-
-  render() {
-    const {
-      title = '',
-      logo,
-      action,
-      content,
-      extraContent,
-      tabList,
-      className,
-      tabActiveKey,
-      tabDefaultActiveKey,
-      tabBarExtraContent,
-      loading = false,
-      wide = false,
-      hiddenBreadcrumb = false,
-    } = this.props;
-
-    const clsString = classNames(styles.pageHeader, className);
-    const activeKeyProps = {};
-    if (tabDefaultActiveKey !== undefined) {
-      activeKeyProps.defaultActiveKey = tabDefaultActiveKey;
-    }
-    if (tabActiveKey !== undefined) {
-      activeKeyProps.activeKey = tabActiveKey;
-    }
-    return (
-      <div className={clsString}>
-        <div className={wide ? styles.wide : ''}>
-          <Skeleton
-            loading={loading}
-            title={false}
-            active
-            paragraph={{ rows: 3 }}
-            avatar={{ size: 'large', shape: 'circle' }}
-          >
-            {hiddenBreadcrumb ? null : <BreadcrumbView {...this.props} />}
-            <div className={styles.detail}>
-              {logo && <div className={styles.logo}>{logo}</div>}
-              <div className={styles.main}>
-                <div className={styles.row}>
-                  <h1 className={styles.title}>{title}</h1>
-                  {action && <div className={styles.action}>{action}</div>}
-                </div>
-                <div className={styles.row}>
-                  {content && <div className={styles.content}>{content}</div>}
-                  {extraContent && <div className={styles.extraContent}>{extraContent}</div>}
-                </div>
-              </div>
-            </div>
-            {tabList && tabList.length ? (
-              <Tabs
-                className={styles.tabs}
-                {...activeKeyProps}
-                onChange={this.onChange}
-                tabBarExtraContent={tabBarExtraContent}
-              >
-                {tabList.map(item => (
-                  <TabPane tab={item.tab} key={item.key} />
-                ))}
-              </Tabs>
-            ) : null}
-          </Skeleton>
-        </div>
-      </div>
-    );
-  }
-}

+ 0 - 161
src/components/PageHeader/index.less

@@ -1,161 +0,0 @@
-@import '~antd/lib/style/themes/default.less';
-
-.pageHeader {
-  padding: 16px 32px 0 32px;
-  background: @component-background;
-  border-bottom: @border-width-base @border-style-base @border-color-split;
-  .wide {
-    max-width: 1200px;
-    margin: auto;
-  }
-  .detail {
-    display: flex;
-  }
-
-  .row {
-    display: flex;
-    width: 100%;
-  }
-
-  .breadcrumb {
-    margin-bottom: 16px;
-  }
-
-  .tabs {
-    margin: 0 0 0 -8px;
-
-    :global {
-      // 1px 可以让选中效果显示完成
-      .ant-tabs-bar {
-        margin-bottom: 1px;
-        border-bottom: none;
-      }
-    }
-  }
-
-  .logo {
-    flex: 0 1 auto;
-    margin-right: 16px;
-    padding-top: 1px;
-    > img {
-      display: block;
-      width: 28px;
-      height: 28px;
-      border-radius: @border-radius-base;
-    }
-  }
-
-  .title {
-    color: @heading-color;
-    font-weight: 500;
-    font-size: 20px;
-  }
-
-  .action {
-    min-width: 266px;
-    margin-left: 56px;
-
-    :global {
-      .ant-btn-group:not(:last-child),
-      .ant-btn:not(:last-child) {
-        margin-right: 8px;
-      }
-
-      .ant-btn-group > .ant-btn {
-        margin-right: 0;
-      }
-    }
-  }
-
-  .title,
-  .content {
-    flex: auto;
-  }
-
-  .action,
-  .extraContent,
-  .main {
-    flex: 0 1 auto;
-  }
-
-  .main {
-    width: 100%;
-  }
-
-  .title,
-  .action {
-    margin-bottom: 16px;
-  }
-
-  .logo,
-  .content,
-  .extraContent {
-    margin-bottom: 16px;
-  }
-
-  .action,
-  .extraContent {
-    text-align: right;
-  }
-
-  .extraContent {
-    min-width: 242px;
-    margin-left: 88px;
-  }
-}
-
-@media screen and (max-width: @screen-xl) {
-  .pageHeader {
-    .extraContent {
-      margin-left: 44px;
-    }
-  }
-}
-
-@media screen and (max-width: @screen-lg) {
-  .pageHeader {
-    .extraContent {
-      margin-left: 20px;
-    }
-  }
-}
-
-@media screen and (max-width: @screen-md) {
-  .pageHeader {
-    .row {
-      display: block;
-    }
-
-    .action,
-    .extraContent {
-      margin-left: 0;
-      text-align: left;
-    }
-  }
-}
-
-@media screen and (max-width: @screen-sm) {
-  .pageHeader {
-    .detail {
-      display: block;
-    }
-  }
-}
-
-@media screen and (max-width: @screen-xs) {
-  .pageHeader {
-    .action {
-      :global {
-        .ant-btn-group,
-        .ant-btn {
-          display: block;
-          margin-bottom: 8px;
-        }
-        .ant-btn-group > .ant-btn {
-          display: inline-block;
-          margin-bottom: 0;
-        }
-      }
-    }
-  }
-}

Разница между файлами не показана из-за своего большого размера
+ 0 - 35
src/components/PageHeader/index.md


+ 0 - 43
src/components/PageHeader/index.test.js

@@ -1,43 +0,0 @@
-import { getBreadcrumb } from './breadcrumb';
-import { urlToList } from '../_utils/pathTools';
-
-const routerData = {
-  '/dashboard/analysis': {
-    name: '分析页',
-  },
-  '/userinfo': {
-    name: '用户列表',
-  },
-  '/userinfo/:id': {
-    name: '用户信息',
-  },
-  '/userinfo/:id/addr': {
-    name: '收货订单',
-  },
-};
-describe('test getBreadcrumb', () => {
-  it('Simple url', () => {
-    expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual('分析页');
-  });
-  it('Parameters url', () => {
-    expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual('用户信息');
-  });
-  it('The middle parameter url', () => {
-    expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual('收货订单');
-  });
-  it('Loop through the parameters', () => {
-    const urlNameList = urlToList('/userinfo/2144/addr').map(
-      url => getBreadcrumb(routerData, url).name
-    );
-    expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']);
-  });
-
-  it('a path', () => {
-    const urlNameList = urlToList('/userinfo').map(url => getBreadcrumb(routerData, url).name);
-    expect(urlNameList).toEqual(['用户列表']);
-  });
-  it('Secondary path', () => {
-    const urlNameList = urlToList('/userinfo/2144').map(url => getBreadcrumb(routerData, url).name);
-    expect(urlNameList).toEqual(['用户列表', '用户信息']);
-  });
-});

+ 116 - 0
src/components/PageHeaderWrapper/breadcrumb.js

@@ -0,0 +1,116 @@
+import React from 'react';
+import pathToRegexp from 'path-to-regexp';
+import Link from 'umi/link';
+import { FormattedMessage } from 'umi-plugin-react/locale';
+import { urlToList } from '../_utils/pathTools';
+
+// 渲染Breadcrumb 子节点
+// Render the Breadcrumb child node
+const itemRender = (route, params, routes, paths) => {
+  const last = routes.indexOf(route) === routes.length - 1;
+  return last || !route.component ? (
+    <span>{route.breadcrumbName}</span>
+  ) : (
+    <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
+  );
+};
+
+const renderItemLocal = item => {
+  if (item.locale) {
+    return <FormattedMessage id={item.locale} defaultMessage={item.name} />;
+  }
+  return item.name;
+};
+
+export const getBreadcrumb = (breadcrumbNameMap, url) => {
+  let breadcrumb = breadcrumbNameMap[url];
+  if (!breadcrumb) {
+    Object.keys(breadcrumbNameMap).forEach(item => {
+      if (pathToRegexp(item).test(url)) {
+        breadcrumb = breadcrumbNameMap[item];
+      }
+    });
+  }
+  return breadcrumb || {};
+};
+
+export const getBreadcrumbProps = props => {
+  const { routes, params, location, breadcrumbNameMap } = props;
+  return {
+    routes,
+    params,
+    routerLocation: location,
+    breadcrumbNameMap,
+  };
+};
+
+// Generated according to props
+const conversionFromProps = props => {
+  const { breadcrumbList } = props;
+  return breadcrumbList.map(item => {
+    const { title, href } = item;
+    return {
+      path: href,
+      breadcrumbName: title,
+    };
+  });
+};
+
+const conversionFromLocation = (routerLocation, breadcrumbNameMap, props) => {
+  const { home } = props;
+  // Convert the url to an array
+  const pathSnippets = urlToList(routerLocation.pathname);
+  // Loop data mosaic routing
+  const extraBreadcrumbItems = pathSnippets.map(url => {
+    const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
+    if (currentBreadcrumb.inherited) {
+      return null;
+    }
+    const name = renderItemLocal(currentBreadcrumb);
+    const { hideInBreadcrumb } = currentBreadcrumb;
+    return name && !hideInBreadcrumb
+      ? {
+          path: url,
+          breadcrumbName: name,
+        }
+      : null;
+  });
+  // Add home breadcrumbs to your head if defined
+  if (home) {
+    extraBreadcrumbItems.unshift({
+      path: '/',
+      breadcrumbName: home,
+    });
+  }
+  return extraBreadcrumbItems;
+};
+
+/**
+ * 将参数转化为面包屑
+ * Convert parameters into breadcrumbs
+ */
+export const conversionBreadcrumbList = props => {
+  const { breadcrumbList } = props;
+  const { routes, params, routerLocation, breadcrumbNameMap } = getBreadcrumbProps(props);
+  if (breadcrumbList && breadcrumbList.length) {
+    return conversionFromProps();
+  }
+  // 如果传入 routes 和 params 属性
+  // If pass routes and params attributes
+  if (routes && params) {
+    return {
+      routes: routes.filter(route => route.breadcrumbName),
+      params,
+      itemRender,
+    };
+  }
+  // 根据 location 生成 面包屑
+  // Generate breadcrumbs based on location
+  if (routerLocation && routerLocation.pathname) {
+    return {
+      routes: conversionFromLocation(routerLocation, breadcrumbNameMap, props),
+      itemRender,
+    };
+  }
+  return {};
+};

+ 90 - 27
src/components/PageHeaderWrapper/index.js

@@ -1,40 +1,103 @@
 import React from 'react';
 import { FormattedMessage } from 'umi-plugin-react/locale';
 import Link from 'umi/link';
-import PageHeader from '@/components/PageHeader';
+import { PageHeader, Tabs, Typography } from 'antd';
 import { connect } from 'dva';
+import classNames from 'classnames';
 import GridContent from './GridContent';
 import styles from './index.less';
 import MenuContext from '@/layouts/MenuContext';
+import { conversionBreadcrumbList } from './breadcrumb';
 
-const PageHeaderWrapper = ({ children, contentWidth, wrapperClassName, top, ...restProps }) => (
-  <div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
-    {top}
-    <MenuContext.Consumer>
-      {value => (
-        <PageHeader
-          wide={contentWidth === 'Fixed'}
-          home={<FormattedMessage id="menu.home" defaultMessage="Home" />}
-          {...value}
-          key="pageheader"
-          {...restProps}
-          linkElement={Link}
-          itemRender={item => {
-            if (item.locale) {
-              return <FormattedMessage id={item.locale} defaultMessage={item.title} />;
-            }
-            return item.title;
+const { Title } = Typography;
+
+/**
+ * render Footer tabList
+ * In order to be compatible with the old version of the PageHeader
+ * basically all the functions are implemented.
+ */
+const renderFooter = ({ tabList, activeKeyProps, onTabChange, tabBarExtraContent }) => {
+  return tabList && tabList.length ? (
+    <Tabs
+      className={styles.tabs}
+      {...activeKeyProps}
+      onChange={key => {
+        if (onTabChange) {
+          onTabChange(key);
+        }
+      }}
+      tabBarExtraContent={tabBarExtraContent}
+    >
+      {tabList.map(item => (
+        <Tabs.TabPane tab={item.tab} key={item.key} />
+      ))}
+    </Tabs>
+  ) : null;
+};
+
+const PageHeaderWrapper = ({
+  children,
+  contentWidth,
+  wrapperClassName,
+  top,
+  title,
+  content,
+  logo,
+  extraContent,
+  ...restProps
+}) => {
+  return (
+    <div style={{ margin: '-24px -24px 0' }} className={classNames(classNames, styles.main)}>
+      {top}
+      {title && content && (
+        <MenuContext.Consumer>
+          {value => {
+            return (
+              <PageHeader
+                wide={contentWidth === 'Fixed'}
+                title={
+                  <Title
+                    level={4}
+                    style={{
+                      marginBottom: 0,
+                    }}
+                  >
+                    {title}
+                  </Title>
+                }
+                key="pageheader"
+                {...restProps}
+                breadcrumb={conversionBreadcrumbList({
+                  ...value,
+                  ...restProps,
+                  home: <FormattedMessage id="menu.home" defaultMessage="Home" />,
+                })}
+                className={styles.pageHeader}
+                linkElement={Link}
+                footer={renderFooter(restProps)}
+              >
+                <div className={styles.detail}>
+                  {logo && <div className={styles.logo}>{logo}</div>}
+                  <div className={styles.main}>
+                    <div className={styles.row}>
+                      {content && <div className={styles.content}>{content}</div>}
+                      {extraContent && <div className={styles.extraContent}>{extraContent}</div>}
+                    </div>
+                  </div>
+                </div>
+              </PageHeader>
+            );
           }}
-        />
+        </MenuContext.Consumer>
       )}
-    </MenuContext.Consumer>
-    {children ? (
-      <div className={styles.content}>
-        <GridContent>{children}</GridContent>
-      </div>
-    ) : null}
-  </div>
-);
+      {children ? (
+        <div className={styles['children-content']}>
+          <GridContent>{children}</GridContent>
+        </div>
+      ) : null}
+    </div>
+  );
+};
 
 export default connect(({ setting }) => ({
   contentWidth: setting.contentWidth,

+ 102 - 3
src/components/PageHeaderWrapper/index.less

@@ -1,11 +1,110 @@
 @import '~antd/lib/style/themes/default.less';
 
-.content {
+.children-content {
   margin: 24px 24px 0;
 }
 
-@media screen and (max-width: @screen-sm) {
+.main {
+  :global {
+    .ant-page-header {
+      padding: 16px 32px 0;
+      background: #fff;
+      border-bottom: 1px solid #e8e8e8;
+    }
+  }
+
+  .wide {
+    max-width: 1200px;
+    margin: auto;
+  }
+  .detail {
+    display: flex;
+  }
+
+  .row {
+    display: flex;
+    width: 100%;
+  }
+
+  .logo {
+    flex: 0 1 auto;
+    margin-right: 16px;
+    padding-top: 1px;
+    > img {
+      display: block;
+      width: 28px;
+      height: 28px;
+      border-radius: @border-radius-base;
+    }
+  }
+
+  .title-content {
+    margin-bottom: 16px;
+  }
+
+  @media screen and (max-width: @screen-sm) {
+    .content {
+      margin: 24px 0 0;
+    }
+  }
+
+  .title,
   .content {
-    margin: 24px 0 0;
+    flex: auto;
+  }
+
+  .extraContent,
+  .main {
+    flex: 0 1 auto;
+  }
+
+  .main {
+    width: 100%;
+  }
+
+  .title {
+    margin-bottom: 16px;
+  }
+
+  .logo,
+  .content,
+  .extraContent {
+    margin-bottom: 16px;
+  }
+
+  .extraContent {
+    min-width: 242px;
+    margin-left: 88px;
+    text-align: right;
+  }
+}
+
+@media screen and (max-width: @screen-xl) {
+  .extraContent {
+    margin-left: 44px;
+  }
+}
+
+@media screen and (max-width: @screen-lg) {
+  .extraContent {
+    margin-left: 20px;
+  }
+}
+
+@media screen and (max-width: @screen-md) {
+  .row {
+    display: block;
+  }
+
+  .action,
+  .extraContent {
+    margin-left: 0;
+    text-align: left;
+  }
+}
+
+@media screen and (max-width: @screen-sm) {
+  .detail {
+    display: block;
   }
 }

+ 4 - 1
src/layouts/Header.js

@@ -120,7 +120,10 @@ class HeaderView extends Component {
     const isTop = layout === 'topmenu';
     const width = this.getHeadWidth();
     const HeaderDom = visible ? (
-      <Header style={{ padding: 0, width }} className={fixedHeader ? styles.fixedHeader : ''}>
+      <Header
+        style={{ padding: 0, width, zIndex: 2 }}
+        className={fixedHeader ? styles.fixedHeader : ''}
+      >
         {isTop && !isMobile ? (
           <TopNavHeader
             theme={navTheme}