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代码如下

    1. export default [
    2. {
    3. path: '/user',
    4. layout: false,
    5. routes: [
    6. {
    7. path: '/user/login',
    8. layout: false,
    9. name: 'login',
    10. component: './user/Login',
    11. },
    12. {
    13. path: '/user',
    14. redirect: '/user/login',
    15. },
    16. {
    17. name: 'register-result',
    18. icon: 'smile',
    19. path: '/user/register-result',
    20. component: './user/register-result',
    21. },
    22. {
    23. name: 'register',
    24. icon: 'smile',
    25. path: '/user/register',
    26. component: './user/register',
    27. },
    28. {
    29. component: '404',
    30. },
    31. ],
    32. },
    33. {
    34. name: 'account',
    35. icon: 'user',
    36. path: '/account',
    37. routes: [
    38. {
    39. path: '/account',
    40. redirect: '/account/center',
    41. },
    42. {
    43. name: 'center',
    44. icon: 'smile',
    45. path: '/account/center',
    46. component: './account/center',
    47. },
    48. {
    49. name: 'settings',
    50. icon: 'smile',
    51. path: '/account/settings',
    52. component: './account/settings',
    53. },
    54. ],
    55. },
    56. {
    57. path: '/home',
    58. name: 'home',
    59. icon: 'smile',
    60. component: './dashboard/analysis/index',
    61. title:'首页',
    62. },
    63. {
    64. name: 'system',
    65. icon: 'BugOutlined',
    66. path: '/system',
    67. routes: [
    68. {
    69. path: '/',
    70. redirect: '/system/dict',
    71. },
    72. {
    73. name: 'dept',
    74. icon: 'PartitionOutlined',
    75. path: '/system/dept',
    76. component: 'system/dept/index',
    77. title:'部门管理',
    78. },
    79. {
    80. name: 'dict',
    81. icon: 'PartitionOutlined',
    82. path: '/system/dict',
    83. component: 'system/dict/index',
    84. title:'字典管理',
    85. },
    86. ],
    87. },
    88. {
    89. path: '/',
    90. redirect: '/home',
    91. },
    92. {
    93. component: './404',
    94. },
    95. ];

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

    1. {
    2. name: 'dept',
    3. icon: 'PartitionOutlined',
    4. path: '/system/dept',
    5. component: 'system/dept/index',
    6. title:'部门管理',
    7. },
    8. {
    9. name: 'dict',
    10. icon: 'PartitionOutlined',
    11. path: '/system/dict',
    12. component: 'system/dict/index',
    13. title:'字典管理',
    14. },

    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

    1. import { Mode } from 'use-switch-tabs';
    2. const Settings= {
    3. navTheme: 'light',
    4. // 拂晓蓝
    5. primaryColor: '#1890ff',
    6. layout: 'side',
    7. contentWidth: 'Fluid',
    8. fixedHeader: false,
    9. fixSiderbar: true,
    10. colorWeak: false,
    11. title: 'Ant Design Pro',
    12. pwa: false,
    13. logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
    14. iconfontUrl: '',
    15. switchTabs: {
    16. mode: Mode.Route,
    17. fixed: true,
    18. reloadable: true,
    19. persistent: {
    20. force: true,
    21. },
    22. },
    23. };
    24. export default Settings;

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

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

    1. import SwitchTabsLayout from './layouts/SwitchTabsLayout';
    2. import defaultSettings from '../config/defaultSettings';
    1. return {
    2. fetchUserInfo,
    3. currentUser,
    4. settings: defaultSettings,
    5. };
    1. const { switchTabs, ...restSettings } = initialState?.settings || {};
    1. rightContentRender: () => (
    2. <RightContent switchTabsReloadable={switchTabs?.mode && switchTabs.reloadable} />
    3. ),
    4. disableContentMargin: false,
    5. waterMarkProps: {
    6. content: initialState?.currentUser?.name,
    7. },
    8. className: switchTabs?.mode && 'custom-by-switch-tabs',
    9. childrenRender: (children, props) => {
    10. const { route } = props;
    11. return (
    12. <SwitchTabsLayout
    13. mode={switchTabs?.mode}
    14. persistent={switchTabs?.persistent}
    15. fixed={switchTabs?.fixed}
    16. routes={route.routes}
    17. footerRender={() => <Footer />}
    18. >
    19. {children}
    20. </SwitchTabsLayout>
    21. );
    22. },

    完整的app.jsx代码如下

    1. import { PageLoading } from '@ant-design/pro-layout';
    2. import { history, Link } from 'umi';
    3. import RightContent from '@/components/RightContent';
    4. import Footer from '@/components/Footer';
    5. import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
    6. import { BookOutlined, LinkOutlined } from '@ant-design/icons';
    7. import { requestInterceptors, responseInterceptors, errorHandler } from '@/utils/Request';
    8. import { getCurrentUserMenus } from './services/ant-design-pro/menu';
    9. import fixMenuItemIcon from '@/utils/fixMenuItemIcon';
    10. import SwitchTabsLayout from './layouts/SwitchTabsLayout';
    11. import defaultSettings from '../config/defaultSettings';
    12. const isDev = process.env.NODE_ENV === 'development';
    13. const loginPath = '/user/login';
    14. /** 获取用户信息比较慢的时候会展示一个 loading */
    15. export const initialStateConfig = {
    16. loading: <PageLoading />,
    17. };
    18. /**
    19. * @see https://umijs.org/zh-CN/plugins/plugin-initial-state
    20. * */
    21. export async function getInitialState() {
    22. const fetchUserInfo = async () => {
    23. try {
    24. const msg = await queryCurrentUser();
    25. const currentUser={...msg.user,permissions: msg.permissions};
    26. console.log("login user info:",currentUser);
    27. return currentUser;
    28. } catch (error) {
    29. history.push(loginPath);
    30. }
    31. return undefined;
    32. };
    33. if (history.location.pathname !== loginPath) {
    34. const token = localStorage.getItem('access_token');
    35. if (!token) {
    36. history.push(loginPath);
    37. return {
    38. fetchUserInfo,
    39. currentUser:{},
    40. settings: defaultSettings,
    41. };
    42. }
    43. const currentUser = await fetchUserInfo();
    44. return {
    45. fetchUserInfo,
    46. currentUser,
    47. settings: defaultSettings,
    48. };
    49. }
    50. return {
    51. fetchUserInfo,
    52. currentUser,
    53. settings: defaultSettings,
    54. };
    55. }
    56. // ProLayout 支持的api https://procomponents.ant.design/components/layout
    57. export const request = {
    58. errorHandler,
    59. requestInterceptors: [requestInterceptors],
    60. responseInterceptors: [responseInterceptors],
    61. };
    62. export const layout = ({ initialState,setInitialState }) => {
    63. const { switchTabs, ...restSettings } = initialState?.settings || {};
    64. return {
    65. rightContentRender: () => (
    66. <RightContent switchTabsReloadable={switchTabs?.mode && switchTabs.reloadable} />
    67. ),
    68. disableContentMargin: false,
    69. waterMarkProps: {
    70. content: initialState?.currentUser?.name,
    71. },
    72. className: switchTabs?.mode && 'custom-by-switch-tabs',
    73. childrenRender: (children, props) => {
    74. const { route } = props;
    75. return (
    76. <SwitchTabsLayout
    77. mode={switchTabs?.mode}
    78. persistent={switchTabs?.persistent}
    79. fixed={switchTabs?.fixed}
    80. routes={route.routes}
    81. footerRender={() => <Footer />}
    82. >
    83. {children}
    84. </SwitchTabsLayout>
    85. );
    86. },
    87. footerRender: () => <Footer />,
    88. onPageChange: () => {
    89. const { location } = history; // 如果没有登录,重定向到 login
    90. if (!initialState?.currentUser && location.pathname !== loginPath) {
    91. history.push(loginPath);
    92. }
    93. },
    94. links: isDev
    95. ? [
    96. <Link to="/umi/plugin/openapi" target="_blank">
    97. <LinkOutlined />
    98. <span>OpenAPI 文档</span>
    99. </Link>,
    100. <Link to="/~docs">
    101. <BookOutlined />
    102. <span>业务组件文档</span>
    103. </Link>,
    104. ]
    105. : [],
    106. menuHeaderRender: undefined,
    107. menu: {
    108. // 每当 initialState?.currentUser?.userId 发生修改时重新执行 request
    109. params: {
    110. userId: initialState?.currentUser?.userId,
    111. },
    112. request: async (params, defaultMenuData) => {
    113. const tempMenuData = await getCurrentUserMenus();
    114. const menuData=fixMenuItemIcon(tempMenuData);
    115. setInitialState({
    116. ...initialState,
    117. menuData: menuData,
    118. });
    119. return menuData;
    120. },
    121. },
    122. // 自定义 403 页面
    123. // unAccessible: <div>unAccessible</div>,
    124. ...restSettings,
    125. };
    126. };

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

    1. @import '~antd/es/style/themes/default.less';
    2. html,
    3. body,
    4. #root {
    5. height: 100%;
    6. }
    7. .colorWeak {
    8. filter: invert(80%);
    9. }
    10. .ant-layout {
    11. min-height: 100vh;
    12. }
    13. .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
    14. left: unset;
    15. }
    16. canvas {
    17. display: block;
    18. }
    19. body {
    20. text-rendering: optimizeLegibility;
    21. -webkit-font-smoothing: antialiased;
    22. -moz-osx-font-smoothing: grayscale;
    23. }
    24. ul,
    25. ol {
    26. list-style: none;
    27. }
    28. @media (max-width: @screen-xs) {
    29. .ant-table {
    30. width: 100%;
    31. overflow-x: auto;
    32. &-thead > tr,
    33. &-tbody > tr {
    34. > th,
    35. > td {
    36. white-space: pre;
    37. > span {
    38. display: block;
    39. }
    40. }
    41. }
    42. }
    43. }
    44. // Compatible with IE11
    45. @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
    46. body .ant-design-pro > .ant-layout {
    47. min-height: 100vh;
    48. }
    49. }
    50. .custom-by-switch-tabs {
    51. .ant-pro-basicLayout-content {
    52. margin: unset;
    53. & .ant-pro-page-container {
    54. margin: unset;
    55. }
    56. }
    57. }

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

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