自定义layout让umi项目菜单支持动态tab切换-bysking

424 阅读1分钟

本文实现umi/umiMax 项目菜单支持动态tab展示,直接看目标效果:

image.png

umi其实已经有现成的方案,参考, 但是我这个方案相对接入快,代码少(有BUG你自己也方便修,hh~),直接上代码:

新建一个组件:src/layouts/tab-layout.tsx,内容如下:

import { Tabs } from 'antd';
import type { ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { useAppData, useLocation, useNavigate, useOutlet } from 'umi';
interface tabLayoutProps {
  /** 用于自定义keepAlive组件等场景 */
  renderElement?: (ele: ReactNode) => ReactNode;
  /** 动态路由需要传入,路由名称映射: {'/welcome': '欢迎页面', ...} */
  routeNameMap?: Record<string, string>;
}
interface typeElement {
  key: string;
  child: ReactNode;
  label: string;
  location: Location;
}
const TabLayout = (props: tabLayoutProps) => {
  const location = useLocation();
  const pathname = location.pathname?.toLowerCase();
  const element = useOutlet();
  const navigate = useNavigate();
  const [keepElements, setKeepElements] = useState<typeElement[]>([]);
  const { routes } = useAppData();
  const getDefaultRoute = () => {
    const resMap = {};
    Object.keys(routes || {}).forEach((key) => {
      const routeObj = routes[key];
      // @ts-ignore
      resMap[`${routeObj.path?.toLowerCase()}`] = routeObj.name;
    });
    return resMap;
  };
  const tabNameMap: Record<string, string> = {
    ...getDefaultRoute(),
    ...(props.routeNameMap || {}),
  };
  const hasTab = (pathnameVal: string) => {
    return keepElements.some((item) => item.key === pathnameVal);
  };
  const closeTab = (key: string) => {
    const index = keepElements.findIndex((item) => item.key === key);
    if (keepElements.length > 1) {
      const newList: typeElement[] = [...keepElements];
      newList.splice(index, 1);
      setKeepElements(newList);
      const target = newList[newList.length - 1];
      const { pathname: targetPath, hash, search } = target?.location;
      navigate(`${targetPath}${search}${hash}`);
    }
  };
  const RenderDom = props.renderElement || (() => element);
  const targetDom = keepElements.find((obj) => obj.key === pathname);
  useEffect(() => {
    const newKpp = keepElements.map((obj) => {
      if (obj.label) {
        return obj;
      }
      const curRouteLowerPath = obj.location.pathname.toLowerCase();
      return {
        ...obj,
        label: tabNameMap[curRouteLowerPath],
      };
    });
    setKeepElements([...newKpp]);
  }, [props.routeNameMap]);
  useEffect(() => {
    if (!hasTab(pathname)) {
      const newList: typeElement[] = [
        // @ts-ignore
        ...keepElements,
        {
          key: pathname,
          label: tabNameMap[pathname],
          child: RenderDom(element),
          // @ts-ignore
          location,
        },
      ];
      setKeepElements(newList);
    }
  }, [location]);
  return (
    <>
      <Tabs
        hideAdd
        onChange={(key: string) => {
          const target = keepElements.find((obj) => obj.key === key);
          const {
            pathname: targetPath,
            hash,
            search,
          } = target?.location as Location;
          navigate(`${targetPath}${search}${hash}`);
        }}
        size='small'
        items={keepElements}
        activeKey={`${location.pathname?.toLowerCase()}`}
        type='editable-card'
        // @ts-ignore
        onEdit={(key: string) => {
          closeTab(key?.toLowerCase());
        }}
      />
      {targetDom?.child || element}
    </>
  );
};
export default TabLayout;

使用: umi项目中在src下新建layouts/index.tsx,导出即可!是不是很快

import React from 'react';

import TabLayout from './tab-layout'; // 上面组件的路径,灵活调整

const LayoutsCmp = (props: typeProps) => {
    return (
        <div>
            <TabLayout></TabLayout>
        </div>
    );
}

export default LayoutsCmp;

如果对你带了实质性的帮助,或者解决了你现在的需求痛点,不妨私信请我喝杯☕咖啡,哈哈哈哈哈哈,嗝~