Ant Design Pro项目如何全局添加返回顶部按钮

1,974 阅读3分钟

背景

在最近的业务中,产品提了一个需求,在页面的右下角添加一个返回顶部的按钮。在 用 React Hooks 实现返回顶部按钮 一文中,自己实现了一个【返回顶部】的按钮。项目使用的是Midway 和 Ant Design Pro 前后端分离方案。那么在使用Ant Design Pro的项目中如何全局添加一个返回顶部按钮呢?

我们来看一下 Ant Design Pro 的目录结构:

├── config                   # umi 配置,包含路由,构建等配置
├── mock                     # 本地模拟数据
├── public
│   └── favicon.png          # Favicon
├── src
│   ├── assets               # 本地静态资源
│   ├── components           # 业务通用组件
│   ├── e2e                  # 集成测试用例
│   ├── layouts              # 通用布局
│   ├── models               # 全局 dva model
│   ├── pages                # 业务页面入口和常用模板
│   ├── services             # 后台接口服务
│   ├── utils                # 工具库
│   ├── locales              # 国际化资源
│   ├── global.less          # 全局样式
│   └── global.ts            # 全局 JS
├── tests                    # 测试工具
├── README.md
└── package.json

在 src 目录下的 layouts 目录下,存放的是全局通用的页面布局文件。官方脚手架默认提供了三个通用布局文件:BasicLayout.tsx、BlankLayout.tsx 和 SecurityLayout.tsx。那么我们应该把 返回顶部 按钮放在哪个页面呢?

SecurityLayout.tsx 文件是用于鉴权的,我们遵循组件单一职责的原则,不建议将 按钮 放在该文件中。

按钮放在 BasicLayout.tsx 文件

BasicLayout.tsx文件是整个应用的页面渲染入口,我们可以将 返回按钮 放在该文件中。

return (
  <>
    <ProLayout
      logo={logo}
      menuHeaderRender={(logoDom, titleDom) => (
        <Link to="/">
          {logoDom}
          {titleDom}
        </Link>
      )}
      onCollapse={handleMenuCollapse}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (menuItemProps.isUrl || menuItemProps.children) {
          return defaultDom;
        }

        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: "/",
          breadcrumbName: "首页"
        },
        ...routers
      ]}
      itemRender={(route, params, routes, paths) => {
        const first = routes.indexOf(route) === 0;
        return first ? (
          <Link to={paths.join("/")}>{route.breadcrumbName}</Link>
        ) : (
            <span>{route.breadcrumbName}</span>
          );
      }}
      footerRender={footerRender}
      menuDataRender={menuDataRender}
      rightContentRender={() => <RightContent />}
      {...props}
      {...settings}
    />
    <BackToTop />
  </>
);

在BasicLayout.tsx文件中导入我们实现的 BackToTop 组件,然后添加到 return 中。但是这样就违背了组件单一职责的原则。BasicLayout.tsx 文件是整个应用的页面渲染入口,我们不应该破坏它的结构,所以也不建议将按钮放在 BasicLayout.tsx 文件中。

那我们还能放在哪里呢?

我们来看看 Ant Design Pro 的路由文件:

export default [
  {
    path: '/',
    component: '../layouts/SecurityLayout',
    routes: [
      {
        path: '/',
        component: '../layouts/BasicLayout',
        routes: [
          {
            path: '/',
            redirect: '/welcome',
          },
          {
            path: '/welcome',
            name: 'welcome',
            icon: 'smile',
            component: './Welcome',
          },
          {
            path: '/admin',
            name: 'admin',
            icon: 'crown',
            component: './Admin'
          },
          {
            component: './404',
          },
        ],
      },
      {
        component: './404',
      },
    ]
  },

  {
    component: './404',
  },
]

在上面路由配置中,Ant Design Pro 会首先渲染 SecurityLayout 组件,然后再渲染 BasicLayout 组件。也就是说他们是嵌套的关系, BasicLayout 是 SecurityLayout 的子组件。那么我们可不可以定义一个用来渲染按钮的 layout 页面呢?

在 Ant Design Pro 中,布局和路由系统是紧密结合的,Ant Design Pro 的路由使用了 Umi 的路由方案,其路由配置信息统一抽离到了 config/config.ts 下,可通过config.ts中的路由配置定义每个页面的布局。

因此我们可以通过路由配置来定义页面的布局,将返回顶部功能添加到全局中。

自定义 layout 文件

我们在 layouts 目录下新建一个 BackToTopLayout.tsx 文件,我们遵循组件单一职责的原则,该文件仅用来渲染 返回顶部 按钮:

import React from 'react';
import { connect } from 'dva';
import { ConnectState, ConnectProps } from '@/models/connect';
import BackToTopBtn from '@/components/BackToTopBtn'

interface BackToTopLayoutProps extends ConnectProps {
  loading?: boolean;
}

interface BackToTopLayoutState { }

class BackToTopLayout extends React.Component<BackToTopLayoutProps, BackToTopLayoutState> {
  state: BackToTopLayoutState = {};

  render() {

    const { children } = this.props;
    return <>
      {children}
      <BackToTopBtn />
    </>
  }
}

export default connect(({ loading }: ConnectState) => ({
}))(BackToTopLayout);

然后在 routes 文件中添加路由,我们将 BackToTopLayout 配置在 SecurityLayout 的下面,BasicLayout 的前面:

export default [
  {
    path: '/',
    component: '../layouts/SecurityLayout',
    routes: [
      {
        path: '/',
        component: '../layouts/BackToTopLayout',
        routes: [
          {
            path: '/',
            component: '../layouts/BasicLayout',
            routes: [
              {
                path: '/',
                redirect: '/welcome',
              },
              {
                path: '/welcome',
                name: 'welcome',
                icon: 'smile',
                component: './Welcome',
              },
              {
                path: '/admin',
                name: 'admin',
                icon: 'crown',
                component: './Admin'
              },
              {
                component: './404',
              },
            ],
          },
          {
            component: './404',
          },
        ]
      }
    ],
  },
  {
    component: './404',
  },
]

下面是我们的实现效果:

image.png

总结

在使用 Ant Design Pro 搭建的项目中,添加一个作用于全局的功能,通常将其功能添加到 layouts 目录下的文件里。我们应当遵循组件单一职责的原则,在 layouts 目录下新建一个 layout 文件,然后在 routes 文件中配置路由,以这样的方式来实现一个作用于全局的功能。