ant-design / pro-components

🏆 Use Ant Design like a Pro!
https://pro-components.antdigital.dev
MIT License
4.04k stars 1.29k forks source link

🧐[问题] ProLayout 如何关闭 location.pathname 匹配,parentKeys 在什么情况下生效 #1199

Closed kids-return closed 3 years ago

kids-return commented 3 years ago

🧐 问题描述

通过后台获取的动态菜单 菜单格式

{
  path: "/a"
  children: [
    {path:"/a/b", parentKeys:["/a"]}, 
    {path:"/a/b/c", parentKeys:["/a"]},
  ]
}

当选中 /a/b/c 时 /a/b 也会选中,还是会进行 pathname 匹配。 因为菜单是后台动态控制的,希望可以随意调整层级的情况下 path 不改变

kids-return commented 3 years ago

ProLayout 会根据 location.pathname 来自动选中菜单,并且自动生成相应的面包屑。如果不想使用可以自己配置 selectedKeys 和 openKeys 来进行受控配置。

selectedKeys 和 parentKeys 之间有什么关系 parentKeys 在何种情况下才会生效 selectedKeys,没找到在哪里设置, 我还是小白水平,这个问题 困扰我好久了

kids-return commented 3 years ago

使用的是 v5 "@ant-design/pro-layout": "^6.4.19",


export const layout = ({
  initialState,
}: {
  initialState: {
    settings?: LayoutSettings;
    currentUser?: API.CurrentUser;
    menus?: any; // 应该是 MenuDataItem[]
  };
}): BasicLayoutProps => {
  console.log('app.initialState', initialState);
  return {
    rightContentRender: () => <RightContent />,
    disableContentMargin: false,
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { currentUser } = initialState;
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!currentUser && location.pathname !== '/user/login') {
        history.push('/user/login');
      }
    },
    menuHeaderRender: undefined,
    menuDataRender: (): MenuDataItem[] => {
      const { menus } = initialState;
      return menus;
    },
    ...initialState?.settings,
  };
};
kids-return commented 3 years ago

ProLayout 会根据 location.pathname 来自动选中菜单,并且自动生成相应的面包屑。如果不想使用可以自己配置 selectedKeys 和 openKeys 来进行受控配置。

我使用这种方式成功了 但面包屑还是根据 pathname 来进行匹配的 parentKeys 要在什么场景下使用呢

kids-return commented 3 years ago

如果能通过 parentKeys 实现应该会更好,不然好繁琐呀

kids-return commented 3 years ago

建议路由和菜单分开,路由做路由的事,菜单做菜单的事,面包屑和菜单绑定 对于用户来说无非是多定义一个菜单文件,动态菜单,动态路由实现起来更方便,结构更清晰。 我排查菜单的问题 还是路由的问题 排查了几个小时,对于我这种新手来说太难了

kids-return commented 3 years ago

我不确定 parentKeys 在哪种场景下使用,初步判断是路由和菜单结合才会生效,因为我分开他们一直没生效过

kids-return commented 3 years ago

@chenshuai2144 大佬能帮我看一下吗,我从早上折腾到现在了 😢

chenshuai2144 commented 3 years ago

其实我没看懂你要干什么

kids-return commented 3 years ago

我想知道菜单的 parentKeys 在什么情况下生效 @chenshuai2144

kids-return commented 3 years ago

我目前是后台获取的动态菜单

  1. parentKeys 不生效
  2. 菜单会根据 location.pathname 自动选中 例如菜单同级的path分别为 /a/b, /a/b/c 当选中 /a/b/c 时会自动选中 /a/b,我期望是 只选中 /a/b/c, 不选中 /a/b
-/a
  -/a/b/c   <-选中时
  -/a/b     <-自动选中

通过 selectedKeys,和 openKeys 自己写逻辑可以解决,但面包屑 还是 /a /a/b /a/b/c

所以在动态菜单的情况下,要如何使 parentKeys 生效,由开发者自己控制选中哪个菜单

kids-return commented 3 years ago

image

chenshuai2144 commented 3 years ago

父子菜单要当成平级使用吗?

kids-return commented 3 years ago

因为是后台控制的呀,后台需要和 RBAC 权限捆绑实现动态菜单,然后希望调整菜单层级的时候不修改后台和前台的路由

kids-return commented 3 years ago

我原本以为可以通过 菜单的 parentKeys 实现,但一直不生效 所以就想知道 parentKeys 使用的场景

可以理解我强制把 三级的 /a/b/c 修改到 二级 /a 下面, 返回的动态菜单的 children 也在 /a 的里面

kids-return commented 3 years ago
{
  path: "/a"
  children: [
    {path:"/a/b", parentKeys:["/a"]}, 
    {path:"/a/b/c", parentKeys:["/a"]},
  ]
}

类似这样,后台可以动态调整 各种层级关系,前后台路由不变,我原本以为可以通过 parentKeys 来实现父级菜单的选中 但不行

kids-return commented 3 years ago

因为是动态菜单,各种层级关系调整是避免不了的 例如调整成这种关系

{
  path: "/a/b/c"
  children: [
    {path:"/a/b", parentKeys:["/a/b/c"]}, 
    {path:"/a", parentKeys:["/a/b/c"]},
  ]
}

我期望通过 parentKeys 来决定选中的上级 而不是 pathname

chenshuai2144 commented 3 years ago

你打破了prolayout的约定 有个黑科技我明给你写个demo,如果没有层级关系还是不要包含了

kids-return commented 3 years ago

后台是个无限级的树,可以各种调整层级 返回对应的 children

kids-return commented 3 years ago

所以我就想知道 parentKeys 在什么情况下生效,因为这个后端会一起返回过来告诉我上级是什么

kids-return commented 3 years ago

我有通过 selectedKeys 和 openKeys 写逻辑实现效果,但面包屑依然还是无法改变。 我本以为按照官方的最佳思路 parentKeys 可以解决 但是测试了很久依然解决不了

kids-return commented 3 years ago

这种情况下就不知道 parentKeys 在哪种情境下可以生效 然后延伸出 如果 routes 和 menu 分开 可能对用户更友好,我瞎猜的

kids-return commented 3 years ago

后台的逻辑 是 判断用户的所有权限列表和菜单的匹配、如果权限和菜单的地址匹配就显示这个菜单 权限列表是后台的 restful 标准的 api 地址

如果调整层级 需要修改权限列表和后台 API 路由 我认为不符合最佳实践 所以一直测试 parentKeys

kids-return commented 3 years ago

https://procomponents.ant.design/components/layout/ 我都要哭了 我真的不知道通过这个 parentKeys 如何控制选中的情况

kids-return commented 3 years ago

app.js


export const layout = ({
  initialState,
  setInitialState,
}: {
  initialState: {
    settings?: LayoutSettings;
    currentUser?: API.CurrentUser;
    menus?: any; // 应该是 MenuDataItem[]
  };
}): BasicLayoutProps => {
  console.log('app.initialState', initialState, setInitialState);

  return {
    rightContentRender: () => <RightContent />,
    disableContentMargin: false,
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { currentUser } = initialState;
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!currentUser && location.pathname !== '/user/login') {
        history.push('/user/login');
      }
    },
    menuHeaderRender: undefined,
    menuDataRender: (): MenuDataItem[] => {
      const { menus } = initialState;
      return menus;
    },
    menuProps: {
      selectedKeys: initialState?.selectedKeys,
      openKeys: initialState?.openKeys,
      onSelect: ({item, key, keyPath, domEvent}) => {
        const { openKeys } = initialState
        console.log(openKeys.length);
        setInitialState({...initialState, selectedKeys: keyPath});
        if (openKeys.length > 1) {
          setInitialState({...initialState, openKeys: [openKeys.pop()]});
        }
        console.log('onClick', item, keyPath);
      },
      onOpenChange: (openKeys) => {
        setInitialState({...initialState, openKeys});
        console.log('onOpenChange', openKeys);
      },
    },
    ...initialState?.settings,
  };
};

我通过 menuProps 可以实现,但是面包屑依然是一样的问题 就想搞明白 parentKeys 的使用情况

chenshuai2144 commented 3 years ago

parentKeys 是混合的,可以强行设置,但是原来的还会再。

[...defaultKeys,...parentKeys ], 最好的解决办法是让后端按照规范来处理菜单,或者猛一点自己的实现菜单的openKeys

chenshuai2144 commented 3 years ago

https://github.com/umijs/route-utils/blob/v1.0.19/src/transformRoute/transformRoute.ts 功能是这个库

kids-return commented 3 years ago

我已经通过上面的代码实现 openKeys 菜单了,然而 面包屑 还会有同样的问题,面包屑 会自动根据 path 里的 / 进行分割 如果后端修改的话 所有的菜单地址里都不能出现 / 分割符,所有的地址都需要改为这样 a a-b a-b-c a-b-c-d 有没有可能设置这个分隔符为别的,或完全关闭

其实也就是分隔符的问题

kids-return commented 3 years ago

我排查的思路和想要的效果如下

  1. 分割符可以关闭或替换
  2. 动态菜单可以根据 parentKeys 自动设置
  3. 动态面包屑可以根据 菜单的 parentKeys 自动设置

那么只需要通过 parentKeys 来控制层级关系即可

chenshuai2144 commented 3 years ago

重复提交 同样的issue ,污染社区的话,我会拉黑并删除你

chenshuai2144 commented 3 years ago

你的需求不适合使用prolayout,我建议你全部自己控制。 面包屑可以用 breadcrumbRender={()=>[]} 关闭,如果你不打算遵守重型组件的约定,那么重型组件对你来说是个负担,不会给你任何提效

kids-return commented 3 years ago

@chenshuai2144 我是为了把三个问题 单独抽离出来方便查看,我不确定三个问题是否属于同一个,上面感觉比较混乱

kids-return commented 3 years ago

遵循约定,当我 /a/b/c/d 有一天要调整到第二级 就需要把菜单地址修改为 /a/b-c-d,对应的是前端的路由也需要修改和重新打包 只要一修改菜单层级遵守约定的话对应的都要修改,要么就全部的路由都要定义为 /a-b-c-d 这种,不然无法满足后台获取菜单 及菜单层级调整

kids-return commented 3 years ago

我不确定我理解的对不对 我是根据 https://procomponents.ant.design/components/layout#%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8A%A0%E8%BD%BD-menu 里面的进行实践的

kids-return commented 3 years ago
export default [
  {
    path: '/',
    name: 'welcome',
    children: [
      {
        path: '/welcome',
        name: 'one',
        children: [
          {
            path: '/welcome/welcome',   <- 这里
            name: 'two',
            exact: true,
          },
        ],
      },
    ],
  },
  {
    path: '/demo',
    name: 'demo',
  },
];
export default [
  {
    path: '/',
    name: 'welcome',
    children: [
      {
        path: '/welcome',
        name: 'one',
      },
    ],
  },
  {
    path: '/welcome-welcome', <- 这里
    name: 'two',
    exact: true,
  },
  {
    path: '/demo',
    name: 'demo',
  },
];

例如这是根据demo通过服务器获取的 当服务器调整的层级 准守约定的话 需要做如下

  1. 修改 /welcome/welcome 到 一个非自动匹配 path 如 /welcome-welcome
  2. 前端修改路由 /welcome/welcome/welcome-welcome
  3. 前端重新打包
  4. 发布部署

服务器获取的菜单无法调整的情况下,服务器获取就没有意义呀,调整都是要打包的 不如直接写前端

这是 fork 官方的服务器获取菜单的例子 https://codesandbox.io/s/wispy-monad-uc33n?file=/customMenu.ts

chenshuai2144 commented 3 years ago

你的这种场景全部的path 都改成 a-b-c ,选中用 parentsKeys 来实现,这样就不用重型组件自己的选中了

kids-return commented 3 years ago

好的 感谢,我决定再学习研究一段时间,改写 pro-layout 组件,将菜单和路由分开 我认为菜单和路由没必要耦合在一起,路由负责路由的事,菜单负责菜单和面包屑的事情,菜单默认开启 路径匹配,关闭路径匹配后通过菜单的 chilren 决定菜单和面包屑的层级关系 我是这么构思的,目前能力还达不到,刚接触不久,就是想做一个后台RBAC + 后台动态菜单的管理功能,太难了

chenshuai2144 commented 3 years ago

很多项目达不到这样的复杂度的。刚开始选型的时候纠结过,不过我们选择了适合大多数人的一套

kids-return commented 3 years ago

再次感谢,我一直认为是可能实现的,是我不会使用的问题,现在已经明确下一步方向了

ideaviewes commented 3 years ago

@kids-return ,最后搞定了吗,我也遇到了这种情况……

cuijiudai commented 1 year ago

app.js

export const layout = ({
  initialState,
  setInitialState,
}: {
  initialState: {
    settings?: LayoutSettings;
    currentUser?: API.CurrentUser;
    menus?: any; // 应该是 MenuDataItem[]
  };
}): BasicLayoutProps => {
  console.log('app.initialState', initialState, setInitialState);

  return {
    rightContentRender: () => <RightContent />,
    disableContentMargin: false,
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { currentUser } = initialState;
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!currentUser && location.pathname !== '/user/login') {
        history.push('/user/login');
      }
    },
    menuHeaderRender: undefined,
    menuDataRender: (): MenuDataItem[] => {
      const { menus } = initialState;
      return menus;
    },
    menuProps: {
      selectedKeys: initialState?.selectedKeys,
      openKeys: initialState?.openKeys,
      onSelect: ({item, key, keyPath, domEvent}) => {
        const { openKeys } = initialState
        console.log(openKeys.length);
        setInitialState({...initialState, selectedKeys: keyPath});
        if (openKeys.length > 1) {
          setInitialState({...initialState, openKeys: [openKeys.pop()]});
        }
        console.log('onClick', item, keyPath);
      },
      onOpenChange: (openKeys) => {
        setInitialState({...initialState, openKeys});
        console.log('onOpenChange', openKeys);
      },
    },
    ...initialState?.settings,
  };
};

我通过 menuProps 可以实现,但是面包屑依然是一样的问题 就想搞明白 parentKeys 的使用情况

不好用啊,item, key, keyPath, domEvent都是 undefined

cuijiudai commented 1 year ago

@kids-return 怎么实现的的,不好用啊