菜单的高级用法

上次修改时间:2021-06-04 10:59:99

Pro 中默认会读取 config/config.tsx 中的 routes 配置作为 ProLayout 的菜单数据来生成菜单,并且配合 plugin-access 还可以很方便的进行菜单的权限管理。这个模式可以满足大部分需求,但是业务的复杂度总是在的,有些时候就需要一些高级的用法。

从服务端请求菜单

在某些情况下,写死的菜单数据可能满足不了我们的需求,Pro 也提供了相应的解决方案来进行远程的菜单数据请求。我们这里需要用到两个 API, menu.requestmenu.params,request 需要传入一个 promise,它会自动托管 loading,params 修改会触发 request 方便重新请求菜单。

具体的代码实现如下,我们可以在 src/app.tsx 定义 layout 对象,并且导出。看起来可能是这样的:

// https://umijs.org/zh-CN/plugins/plugin-layout
export const layout: RunTimeLayoutConfig = ({ initialState }) => {
  return {
    menu: {
      // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
      params: {
        userId: initialState?.currentUser?.userid,
      },
      request: async (params, defaultMenuData) => {
        // initialState.currentUser 中包含了所有用户信息
        const menuData = await fetchMenuData();
        return menuData;
      },
    },
  };
};

以上就是一个远程请求菜单的案例,一般情况的菜单都是根据角色来实现的,我们可以配置 initialState 中的数据来向后端请求不同的数据。

如果你的数据希望通过 initialState 来保存,你可以在 request 中直接读取,这样每次 initialState 变化都会重新加载菜单。

// https://umijs.org/zh-CN/plugins/plugin-layout
export const layout: RunTimeLayoutConfig = ({ initialState }) => {
  return {
    menu: {
      // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
      params: initialState,
      request: async (params, defaultMenuData) => {
        return initialState.menuData;
      },
    },
  };
};

如果你希望可以手动的控制菜单刷新,可以使用 actionRef 功能。

import { createRef } from 'react';

export const layoutActionRef = createRef<{ reload: () => void }>();

// https://umijs.org/zh-CN/plugins/plugin-layout
export const layout: RunTimeLayoutConfig = () => {
  return {
    actionRef: layoutActionRef,
    menu: {
      request: async (params, defaultMenuData) => {
        return initialState.menuData;
      },
    },
  };
};

// 调用
request().then(() => {
  layoutActionRef.current.reload();
});

自定义高亮

大部分时候菜单的高亮是可以通过父子关系来满足的,我们也推荐这种用法。假如有一个增删改查的页面,可以如下设置:

export default {
  path: '/product',
  name: '产品管理',
  routes: [
    { path: '/product', redirect: 'product/list' },
    {
      path: '/product/list',
      name: '产品列表',
    },
    {
      path: '/product/new',
      name: '新建产品',
    },
    {
      path: '/product/:id',
      hideInMenu: true,
      name: '产品详情',
    },
    {
      path: '/product/update/:id',
      hideInMenu: true,
      name: '修改产品',
    },
  ],
};

这样的路由非常标准,可以被 ProLayout 完美的消费,高亮也能正确展示,但是不一定所有的菜单的都可以做的这么规范,ProLayout 也提供一个方式来重定向菜单的高亮。如果我们想要 /list/:id,高亮 /product可以这样配置。

export default [
  {
    path: '/product',
    hideInMenu: true,
    name: '产品管理',
  },
  {
    path: '/list/:id',
    hideInMenu: true,
    name: '列表详情',
    parentKeys: ['/product'],
  },
];

这样就可以在/list/:id路径的时候,也高亮 /product, parentKeys 中的 key 一般是路径,如果不方便设置为路径的话可以在 菜单配置中增加 key 属性,Layout 会优先使用配置的 Key 属性。

根据路径更换布局

在一些复杂的路径中我们可以需要根据不同的 url 展示不同的界面,比如在新建的时候我们是不需要左侧菜单,如果用传统的方法实现需要根据 pathname 来进行不同的配置。实现成本比较高,为了降低实现成本,我们在 routers 配置中增加了一些约定。

export default [
  {
    path: '/product',
    // 不展示菜单
    menuRender: false,
    name: '产品管理',
  },
  {
    path: '/list/:id',
    // 编辑的时候使用顶部菜单
    layout: 'top',
    name: '列表详情',
    parentKeys: ['/product'],
  },
];

这样在 /product 的时候不展示菜单,在 /list/:id 中的时候展示了顶部菜单。在菜单中可以配置以下的 api。

export interface Setting {
  /**
   * @name false 时不展示顶栏
   */
  headerRender?: false;
  /**
   * @name false 时不展示页脚
   */
  footerRender?: false;
  /**
   * @name false 时不展示菜单
   */
  menuRender?: false;
  /**
   * @name false 时不展示菜单顶栏
   */
  menuHeaderRender?: false;

  /**
   * @name 固定顶栏
   **/
  fixedHeader: boolean;

  /**
   * @name 固定菜单
   */
  fixSiderbar: boolean;

  /**
   * @name theme for nav menu
   * @name 导航菜单的主题
   */
  navTheme: 'dark' | 'light' | 'realDark' | undefined;
  /**
   * @name nav menu position: `side` or `top`
   * @name 导航菜单的位置
   * @description side 为正常模式,top菜单显示在顶部,mix 两种兼有
   */
  layout: 'side' | 'top' | 'mix';
  /**
   * @name 顶部导航的主题,mix 模式生效
   */
  headerTheme: 'dark' | 'light';
}