多页签Tab栏效果

3,217 阅读5分钟

1.官方默认不支持多tab,我们要实现多页签效果, 先要至少有2个页面来进行切换,我们现在的菜单是从数据库中获取的动态菜单,对应的页面并未实现,所以第一步我们挑两个页面来实现简单的列表效果 image.png

2.这里挑了字典管理、部门管理两个页面,来实现多页签效果。由于代码较多,我们无法象之前的章节一样一步步来操作了,只描述下目录结构和文件组成,本节所有代码都已经在码云上提交。另字典管理、和部门管理两块的代码借鉴了若依React版,若依React版最新版已经支持Antd Pro V5,若依React版是TypeScript,字典管理、和部门管理列表页面的代码基本上是基于若依React的TypeScript翻成了JS版本。

3.修改了config目录下的config.js和routes.js,routes.js中只放我们要访问的部分页面,config.js中引入routes.js,完整的routes.js代码如下

export default [  {    path: '/user',    layout: false,    routes: [      {        path: '/user/login',        layout: false,        name: 'login',        component: './user/Login',      },      {        path: '/user',        redirect: '/user/login',      },      {        name: 'register-result',        icon: 'smile',        path: '/user/register-result',        component: './user/register-result',      },      {        name: 'register',        icon: 'smile',        path: '/user/register',        component: './user/register',      },      {        component: '404',      },    ],
  },

  {
    name: 'account',
    icon: 'user',
    path: '/account',
    routes: [
      {
        path: '/account',
        redirect: '/account/center',
      },
      {
        name: 'center',
        icon: 'smile',
        path: '/account/center',
        component: './account/center',
      },
      {
        name: 'settings',
        icon: 'smile',
        path: '/account/settings',
        component: './account/settings',
      },
    ],
  },
  {
    path: '/home',
    name: 'home',
    icon: 'smile',
    component: './dashboard/analysis/index',
    title:'首页',
  },
  {
    name: 'system',
    icon: 'BugOutlined',
    path: '/system',
    routes: [
      {
        path: '/',
        redirect: '/system/dict',
      },

      {
        name: 'dept',
        icon: 'PartitionOutlined',
        path: '/system/dept',
        component: 'system/dept/index',
        title:'部门管理',
      },

      {
        name: 'dict',
        icon: 'PartitionOutlined',
        path: '/system/dict',
        component: 'system/dict/index',
        title:'字典管理',
      },
    ],
  },

  {
    path: '/',
    redirect: '/home',
  },
  {
    component: './404',
  },
];

4.routes里面配置了字典管理和部门管理两个页面的路由及对应组件信息

  {
        name: 'dept',
        icon: 'PartitionOutlined',
        path: '/system/dept',
        component: 'system/dept/index',
        title:'部门管理',
      },

      {
        name: 'dict',
        icon: 'PartitionOutlined',
        path: '/system/dict',
        component: 'system/dict/index',
        title:'字典管理',
      },

5 src/pages/system下的dept和dict目录结构及文件如下图所示 image.png

index.jsx是列表首页,service.js封装了用umi request调用后台服务的接口,components中封装了子组件。v5的umi request简化了操作,不再需要dva中的model和connect操作。

6.部门管理要用到字典管理中部分封装代码,字典管理列表页面效果如下图 image.png

7.部门管理的列表页面效果如下图 image.png

8.接下来开始做多页签Tab效果,这里用的第三方组件来实现,用了3,4个第三方组件,踩了几个坑,最后用这个组件基本实现了预期效果。

9.多页签Tab组件使用操作步骤1,修改config/defaultSettings.js

import { Mode } from 'use-switch-tabs';
const Settings= {
  navTheme: 'light',
  // 拂晓蓝
  primaryColor: '#1890ff',
  layout: 'side',
  contentWidth: 'Fluid',
  fixedHeader: false,
  fixSiderbar: true,
  colorWeak: false,
  title: 'Ant Design Pro',
  pwa: false,
  logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
  iconfontUrl: '',

  switchTabs: {
    mode: Mode.Route,
    fixed: true,
    reloadable: true,
    persistent: {
      force: true,
    },
  },
};

export default Settings;

10.步骤2src/layouts增加目录和文件 image.png

11.步骤3 src/app.jsx中多处调整

import SwitchTabsLayout from './layouts/SwitchTabsLayout';
import defaultSettings from '../config/defaultSettings';
  return {
      fetchUserInfo,
      currentUser,
      settings: defaultSettings,
    };
  const { switchTabs, ...restSettings } = initialState?.settings || {};
rightContentRender: () => (
      <RightContent switchTabsReloadable={switchTabs?.mode && switchTabs.reloadable} />
    ),
    disableContentMargin: false,
    waterMarkProps: {
      content: initialState?.currentUser?.name,
    },
    className: switchTabs?.mode && 'custom-by-switch-tabs',
    childrenRender: (children, props) => {
      const { route } = props;
      return (
        <SwitchTabsLayout
          mode={switchTabs?.mode}
          persistent={switchTabs?.persistent}
          fixed={switchTabs?.fixed}
          routes={route.routes}
          footerRender={() => <Footer />}
        >
          {children}
        </SwitchTabsLayout>
      );
    },

完整的app.jsx代码如下

import { PageLoading } from '@ant-design/pro-layout';
import { history, Link } from 'umi';
import RightContent from '@/components/RightContent';
import Footer from '@/components/Footer';
import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
import { BookOutlined, LinkOutlined } from '@ant-design/icons';
import { requestInterceptors, responseInterceptors, errorHandler } from '@/utils/Request';
import { getCurrentUserMenus } from './services/ant-design-pro/menu';
import fixMenuItemIcon from '@/utils/fixMenuItemIcon';
import SwitchTabsLayout from './layouts/SwitchTabsLayout';
import defaultSettings from '../config/defaultSettings';

const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
/** 获取用户信息比较慢的时候会展示一个 loading */

export const initialStateConfig = {
  loading: <PageLoading />,
};
/**
 * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
 * */

 export async function getInitialState() {
  const fetchUserInfo = async () => {
    try {
      const msg = await queryCurrentUser();
      const currentUser={...msg.user,permissions: msg.permissions};
      console.log("login user info:",currentUser);
      return currentUser;
    } catch (error) {
      history.push(loginPath);
    }
    return undefined;
  };

  if (history.location.pathname !== loginPath) {
    const token = localStorage.getItem('access_token');
    if (!token) {
      history.push(loginPath);
      return {
        fetchUserInfo,
        currentUser:{},
        settings: defaultSettings,
      };
    }
    const currentUser = await fetchUserInfo();

    return {
      fetchUserInfo,
      currentUser,
      settings: defaultSettings,
    };
  }

  return {
    fetchUserInfo,
    currentUser,
    settings: defaultSettings,
  };
}
// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const request = {
  errorHandler,
  requestInterceptors: [requestInterceptors],
  responseInterceptors: [responseInterceptors],
};
export const layout = ({ initialState,setInitialState  }) => {
  const { switchTabs, ...restSettings } = initialState?.settings || {};

  return {
  rightContentRender: () => (
      <RightContent switchTabsReloadable={switchTabs?.mode && switchTabs.reloadable} />
    ),
    disableContentMargin: false,
    waterMarkProps: {
      content: initialState?.currentUser?.name,
    },
    className: switchTabs?.mode && 'custom-by-switch-tabs',
    childrenRender: (children, props) => {
      const { route } = props;
      return (
        <SwitchTabsLayout
          mode={switchTabs?.mode}
          persistent={switchTabs?.persistent}
          fixed={switchTabs?.fixed}
          routes={route.routes}
          footerRender={() => <Footer />}
        >
          {children}
        </SwitchTabsLayout>
      );
    },
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { location } = history; // 如果没有登录,重定向到 login

      if (!initialState?.currentUser && location.pathname !== loginPath) {
        history.push(loginPath);
      }
    },
    links: isDev
      ? [
          <Link to="/umi/plugin/openapi" target="_blank">
            <LinkOutlined />
            <span>OpenAPI 文档</span>
          </Link>,
          <Link to="/~docs">
            <BookOutlined />
            <span>业务组件文档</span>
          </Link>,
        ]
      : [],
    menuHeaderRender: undefined,
    menu: {
      // 每当 initialState?.currentUser?.userId 发生修改时重新执行 request
      params: {
        userId: initialState?.currentUser?.userId,
      },
      request: async (params, defaultMenuData) => {

        const tempMenuData = await getCurrentUserMenus();
        const menuData=fixMenuItemIcon(tempMenuData);
        setInitialState({
          ...initialState,
          menuData: menuData,
        });
        return menuData;
      },
    },

    // 自定义 403 页面
    // unAccessible: <div>unAccessible</div>,
    ...restSettings,
  };
};

12.步骤4,修改src/global.less,组件文档上未说这一步,导致我的页签位置效果始终不对,对着组件源码比较了很久,才发现要改global.less 样式文件,改完样式,效果基本就对了。

@import '~antd/es/style/themes/default.less';

html,
body,
#root {
  height: 100%;
}

.colorWeak {
  filter: invert(80%);
}

.ant-layout {
  min-height: 100vh;
}
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
  left: unset;
}

canvas {
  display: block;
}

body {
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

ul,
ol {
  list-style: none;
}

@media (max-width: @screen-xs) {
  .ant-table {
    width: 100%;
    overflow-x: auto;
    &-thead > tr,
    &-tbody > tr {
      > th,
      > td {
        white-space: pre;
        > span {
          display: block;
        }
      }
    }
  }
}

// Compatible with IE11
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
  body .ant-design-pro > .ant-layout {
    min-height: 100vh;
  }
}

.custom-by-switch-tabs {
  .ant-pro-basicLayout-content {
    margin: unset;

    & .ant-pro-page-container {
      margin: unset;
    }
  }
}

13.完整的多页Tab栏效果一开始已有截图,我们补一个右键操作的效果截图 image.png

14.到此节,一个比较完整的前端React Antd Pro V5的架子已经搭好,我们只要根据业务的需要,再往里面增加模块即可,由于若依React珠玉在前,且新版的若依React已经使用了Antd Pro V5,若依React用的是现在主流的TypeScript,我们没必要再把若依React的所有功能再做一遍,我的这个系列的文章算是抛砖引玉,让大家熟悉了解Antd Pro V5的一些基本操作和原理。

最近做了个小API应用,希望大家关注支持下: www.yuque.com/docs/share/…