网友需求 - Ant Design Pro 内置的多 tabs 布局

9,149 阅读6分钟

image.png

前言

在四个月前,我随手帮一个网友完成了一个需求,在 antd pro 中增加多 tabs 布局,详见 网友需求 - 使用 50 行代码在 Ant Design Pro 中完成 Umi 状态保持的多tabs布局

真的只是随手为之,因为多 tabs 在 pro 项目的需求,一直被以“与设计不符”为由拒绝了。(当然我也以这个理由拒绝过产品。)这个讨论由来已久,近期 pro issues 中讨论的也很少,我以为应该已经不会有人需要这样的需求了。

没想到,新的多 tabs 方案发出之后,社区上关于这东西的讨论还挺多的,网友又疯狂的给我提需求:

  • 允许设置“首页” tab 不关闭
  • 允许动态修改 tab 标题
  • 和 pro layout 组合使用
  • 允许和 pro 的国际化方案组合使用
  • 允许动态设置哪些页面需要放到 tab 里
  • 允许 tabs 带 icon
  • 增加菜单 - 可关闭左侧 - 可关闭右侧 - 可关闭其它
  • 只剩一个 tab 时隐藏关闭按钮

由于感兴趣的朋友不少,所以在这里重新整理了一下需求和用法,包括为什么这么设计,欢迎各位参与讨论。

共识

这个方案只能用在 umi@4 系列的项目中,比如 alita@3 、 @umijs/max 和 kmi 等。 umi@3 系列的我之前也写过一个 demo 插件,那个不能用了,懒得维护了,所以如果你需要多 tabs 能力,请先升级到 umi@4 。或者自己参考实现写一个 umi@3 的。

因为多 tabs 的基本要求就是在 tab 中的页面,应该被 “状态保持”,因此这个能力的基础是 keepalive,所以实际上这个插件是放到 keepalive 的插件中实现的,tabs-layout 插件只是增加了一个配置,供用户开启和关闭功能使用。所以你会在使用多 tabs 的时候,用到一些 keepalive 插件的 api。

最简单的理解应该是,被“标记为需要状态保持”的页面,会被放入 tabs 中。这个“标记”是 keepalive 实现的。

为什么不独立出来

因为这两个插件共用一个临时文件,是通过同一个 tpl 文件生成的。这玩意拆不开。

这里顺便吐槽一下 tpl 真的是反人类,我和陈杰两个人最近写 tpl 写吐了,你可以看到这阶段我们发了很多包,几乎都是因为 tpl 改错了,改新功能的时候,影响了旧功能。

配置说明

keepalive,

keepalive: [/./],

keepalive 的配置项,支持正则表达式。但是所有的路由正则匹配应该是全小写的,比如不管你的路由是 homeHome 还是 hoMe ,只有设置 keepalive:[/home/] 才有效。而字符串的配置方式就刚好相反,如果你的路由是home,你配置 homeHome 还是 hoMe 都有效。

/./ 表示全匹配,也就是全部的路由都保持

tabsLayout

  tabsLayout: {
    hasDropdown: true,
  },

开启 tabs 功能,hasDropdown 表示是否使用默认的右侧功能,关闭左侧,右侧其它等。

用法

首先你应该合理的把方案集成到你的系统中,请注意区分,你现在用的是什么框架。

我知道你下意识的会回答 “umi” 框架。请按下面分类认证区分。

umi 项目中使用

这里的 umi 项目指的是没有使用多余的插件集合的 umi@4 项目,最好的区分方式是你启动项目时,执行的是 umi dev

安装模块

pnpm i @alita/plugins

配置

config/config.ts 或者 .umirc.ts

export default {
  plugins: [
    require.resolve('@alita/plugins/dist/keepalive'),
    require.resolve('@alita/plugins/dist/tabs-layout'),
  ],
  keepalive: [/./],
  tabsLayout: {
    hasDropdown: true,
  },
}  

运行时配置

运行时配置,在你需要的时候选用,比如要匹配tabs 的名称和 icons 可以配置 tabsLayout,像自定义 tabs 时,则配置 getCustomTabs

 export async function tabsLayout() {
   return {
       local:{
         '/':'首页',
         '/users':'用户',
         '/foo':'其他'
       },
       icons:{
           // 组件哦
           '/':DesktopOutlined
       },
   };
 }

export const getCustomTabs = (config) => {
    // 一些需要的配置
    return <>自定义的 tabs 组件<>
}

使用 useKeepOutlets

在你的全局layout 中使用 useKeepOutlets 代替 Outlet 或者 useOutlet,比如 src/layouts/index.tsx

import { useKeepOutlets } from 'umi';
  
const Layout = () => {
   const element = useKeepOutlets();
   return <>{element}</>;
};
  
export default Layout;

请充分理解上面的用法,这就是这个方案在 umi 插件中需要用到的几处配置,关键有三处,配置 plugins,config 里面配置开关,layout 中使用 useKeepOutlets。

以下几种方式,都是 umi 系的框架,也就是对上面的配置和使用做了隐藏。

alita

alita 中内置了这个插件所以你只需要如下使用

有 github 的朋友帮忙点一个 star,想要一个独立完成的 1k star 的项目

安装模块

无需额外安装

配置

因为 alita 中内置 keepalive 和 tabsLayout 插件的引入,所以你不需要额外安装和配置 plugins

export default {
  appType: 'pc',
  keepalive: [/./],
  tabsLayout: {
    hasDropdown: true,
  },
}  

运行时配置

与上面的一致,无特殊点

使用 useKeepOutlets

与上面的一致,无特殊点

@umijs/max

max 系列的项目,因为自带了布局和国际化,初始化数据这些功能,所以多 tabs 插件做了兼容。

安装模块

pnpm i @alita/pluins

配置

config/config.ts 或者 .umirc.ts

export default {
  plugins: [
    require.resolve('@alita/plugins/dist/keepalive'),
    require.resolve('@alita/plugins/dist/tabs-layout'),
  ],
  keepalive: [/./],
  tabsLayout: {
    hasDropdown: true,
  },
  // 以下是 max 项目的配置
  antd: {},
  locale: {
    // default zh-CN
    default: 'zh-CN',
    antd: true,
    baseNavigator: true,
  },
  layout: {
    locale: true,
  },
  model: {},
  initialState: {},
}  

运行时配置

 // 注意这里 传入了 initialState
 export async function tabsLayout({ initialState }) {
   return {
       local:{},
       icons:{},
   };
 }

export const getCustomTabs = (config) => {
    // 一些需要的配置
    return <>自定义的 tabs 组件<>
}

使用 useKeepOutlets

方法一,与上述相同,与方法二,二选一即可。

方法二,在 layout 的 runtime 配置里面增加

这里是 app.tsx 文件中配置,详见 layout 插件的文档。

import { useKeepOutlets } from '@umijs/max';

export const layout: RunTimeLayoutConfig = ({
  initialState,
  setInitialState,
}) => {
  return {
    childrenRender: (_, props) => {
      const element = useKeepOutlets();
      return (
        <>
          {element}
        </>
      );
    },
    ...
  };
};

pro 项目

这里的 pro 项目指的是 ant-design-pro,版本是 6.0.0-beta.1 之后。

其实 pro 就是 max 加上了 umi-presets-pro

就是帮你加了一下 plugins 配置,主要增加使用了这几个插件,说明在下文注释里面。

  const plugins = [
    // 一些 pro 项目需要的配置
    require.resolve('./features/proconfig'),
    // 默认使用了上面提到的 “使用 useKeepOutlets”
    require.resolve('./features/maxtabs'),
    // opengapi 不是 多 tabs 需要的
    require.resolve('@umijs/max-plugin-openapi'),
    // 帮你配置 keepalive 和 tabs-layout
    require.resolve('@alita/plugins/dist/keepalive'),
    require.resolve('@alita/plugins/dist/tabs-layout'),
  ];

安装模块

无需额外安装

配置

export default {
  // 增加 配置 
  keepalive: [/./],
  tabsLayout: {
    hasDropdown: true,
  },
}  

运行时配置

与 max 系列项目相同

使用 useKeepOutlets

无需其它配置使用,因为在 umi-presets-pro 已经通过插件的方式使用了,做的事情一模一样。

API

API 通过 const { updateTab, dropByCacheKey } = React.useContext(KeepAliveContext);

dropByCacheKey

刷新某个路由

dropByCacheKey(location.pathname)

updateTab

更新 tab 信息,支持更新 icon name 和 关闭按钮

updateTab(location.pathname, {
      icon: <UserOutlined />,
      name: 'hahaha' + Math.ceil((Math.random() * 100) / 10),
      closable: false,
    });

以下是个实例,供参考

import { UserOutlined } from '@ant-design/icons';
import { KeepAliveContext, useLocation, useParams } from 'alita';
import { Button } from 'antd';
import React, { useEffect, useState } from 'react';

export default () => {
  const [count, setCount] = useState(0);
  const location = useLocation();
  const { updateTab } = React.useContext(KeepAliveContext);
  const params = useParams();

  const handleClick = () => {
    updateTab(location.pathname, {
      icon: <UserOutlined />,
      name: 'hahaha' + Math.ceil((Math.random() * 100) / 10),
      closable: false,
    });
  };

  return (
    <div>
      <h3>当前页面状态被设置成自动保存</h3>
      <h3>当前计数是:{count}</h3>
      <Button
        color="primary"
        block
        size="large"
        onClick={() => setCount(count + 1)}
      >
        点我计数加1
      </Button>
      <Button onClick={handleClick}>修改tab</Button>
    </div>
  );
};

总结

好复杂,看不懂,更新 pro 最新的代码,配置使用

  keepalive: [/./],
  tabsLayout: {
    hasDropdown: true,
  },

感谢

以上的网友需求,大部分是 chj-damon 完成的,在此特别感谢。

感谢阅读,关于多 tabs ,如果你有什么想说的,欢迎交流。