美文网首页react + dva + antd
antd pro 动态菜单与动态路由

antd pro 动态菜单与动态路由

作者: 云音流 | 来源:发表于2019-04-27 11:30 被阅读141次

    文章转自我的语雀:https://www.yuque.com/liuyin-zzwa0/ada6ao/va6p77

    项目中使用了动态菜单,其中含有固定和非固定的菜单,但是我们的后端同事只在登陆时返回含有动态菜单name的数组。

    如何实现?

    菜单生成过程

    阅读源码我们可以发现,在 layouts/BasicLayout中可以看到 menuData 是从 menu.js 中获取的

    export default connect(({ global, setting, menu: menuModel }) => ({
      collapsed: global.collapsed,
      layout: setting.layout,
      menuData: menuModel.menuData,
      breadcrumbNameMap: menuModel.breadcrumbNameMap,
      ...setting,
    }))(props => (
      <Media query="(max-width: 599px)">
        {isMobile => <BasicLayout {...props} isMobile={isMobile} />}
      </Media>
    ));
    

    找到 menuData 的生成方式

    image.pngimage.png

    routes 就是运行生成的.umi/router.js 的内容,将其输出我们可以直观的看见它的具体结构

    image.pngimage.png

    修改配置路由

    router.config.js 中使用 dynamic 去标志该目录(路由)是动态的

    {
            path: '/profile',
            name: 'profile',
            icon: 'profile',
            dynamic: true,
            routes: [
              {
                path: '/profile/basic',
                name: 'basic',
                component: './Profile/BasicProfile',
              },
              {
                path: '/profile/basic/:id',
                name: 'basic',
                hideInMenu: true,
                component: './Profile/BasicProfile',
              },
              {
                path: '/profile/advanced',
                name: 'advanced',
                authority: ['admin'],
                dynamic: true,
                component: './Profile/AdvancedProfile',
              },
            ],
    },
    

    过滤实际路由

    大体思路就是对 routes 进行数据处理,将登录获取到的特定路由(这里用 name 属性标识)在 routes 中过滤出来

     effects: {
        *getMenuData({ payload }, { put }) {
          const { routes, authority } = payload;
          const routerMap = filterRouters(routes); // 这边取出实际的路由表
          const menuData = filterMenuData(memoizeOneFormatter(routerMap, authority));
          // const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(menuData);
          const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(memoizeOneFormatter(routerMap, authority)); //这是修改后的代码,因为menuData是已经过滤掉存在hideInMenu
          yield put({
            type: 'save',
            payload: { menuData, breadcrumbNameMap, routerMap, routerData: routes }, // 将实际的路由一同放入
          });
        },
      },
        
    
    const filterRouters = routes => {
      if (!routes) return [];
      const sectionlist = ['profile.advanced']; // 数组中就是包含的动态路由
      const sectionList = sectionlist.filter(item => item && isString(item));
      const oneLevel = sectionList.filter(item => item.split('.').length >= 1).map(item => item.split('.')[0]);
      const levelMore = sectionList.filter(item => item.split('.').length > 1);
      routes = filterLevel1(routes, oneLevel);
      let list = [];
      routes.forEach(route => {
        let deep = 0;
        list.push(filyer(levelMore, route, deep));
      });
      return list.filter(item => item);
    }
    
    const filterLevel1 = (routes, names) => {
      return routes.filter(route => !route.dynamic || (route.dynamic && names.includes(route.name)));
    }
    
    const filyer = (sectionList, route, deep) => {
      let obj = { ...route };
      let sections = sectionList.filter(s => s.indexOf(route.name) === 0);
      if (!route.dynamic && (!obj.routes || !obj.routes.length)) return obj;
      if (!sections.length && route.dynamic && deep !== 0) {
        return null;
      }
      let oneLevel = sections.filter(item => item.split('.').length >= 2).map(item => item.split('.')[1]);
      let levelMore = sections.filter(item => item.split('.').length > 2).map(item => item.split('.').slice(1).join('.'));
      obj.routes = filterLevel1(obj.routes, oneLevel);
      if (levelMore.length) {
        let list = [];
        deep++;
        obj.routes.forEach(item => {
          list.push(filyer(levelMore, item, deep));
        });
        obj.routes = list.filter(a => a);
      }
      return obj;
    }
    

    此时的 menuData 就是我们想要的实际菜单了

    应用动态路由

    实现不跳转的页面 404 、403

    旧版本(2.x)回到 layouts/BasicLayout 中,从props中取出 routerMap

    const baseRouterConfig = this.getRouterAuthority(pathname, routes); // 完整的项目路由
    const routerConfig = this.getRouterAuthority(pathname, routerMap); // 账号的实际路由
    

    权限组件 Authorized ,使用这个组件就能实现不跳转的 404、403页面了

    <Content className={styles.content} style={contentStyle}>
      {/* 先校验完整的项目路由 */}
      <Authorized authority={baseRouterConfig} noMatch={<Exception404 />}>
         {/* 校验账号的实际路由 */}
        <Authorized authority={routerConfig} noMatch={<Exception403 />}>
          {children}
        </Authorized>
      </Authorized>
    </Content>
    
     新版本(3.x)在 layouts/BasicLayout 组件中移除了权限组件并在 `pages/Authorized.js`使用了登录状态判断,但这样同样会出现[嵌套路由](https://www.yuque.com/liuyin-zzwa0/ada6ao/qi3n6u) umi404的情况,因此我们需要对 `pages/Authorized.js` 进行改造。
    
    import React from 'react';
    import Redirect from 'umi/redirect';
    import pathToRegexp from 'path-to-regexp';
    import { connect } from 'dva';
    import Authorized from '@/utils/Authorized';
    import { getAuthority } from '@/utils/authority';
    import Exception403 from '@/pages/Exception/403';
    import Exception404 from '@/pages/Exception/404';
    //import { checkpass } from '@/pages/RoutingCheck';
    
    const checkpass = (routeData = [], pathname) => {
      let pass = false;
      if (!routeData || !routeData.length) return true;
      routeData.forEach(route => {
        if (pathToRegexp(`${route.path}`).exec(pathname)) {
          pass = true;
        } else if (!pass && route.routes) {
          pass = checkpass(route.routes, pathname)
        }
      });
      return pass;
    }
    
    function AuthComponent({ children, location, routerData, routerMap }) {
      const { pathname } = location;
      const auth = getAuthority();
      const isLogin = auth && auth[0] !== 'guest';
      const getRouteAuthority = (path, routeData = []) => {
        let authorities;
        routeData.forEach(route => {
          // match prefix
          if (pathToRegexp(`${route.path}(.*)`).test(path)) {
            authorities = route.authority || authorities;
    
            // get children authority recursively
            if (route.routes) {
              authorities = getRouteAuthority(path, route.routes) || authorities;
            }
          }
        });
        return authorities;
      };
      const baseAuthority = getRouteAuthority(pathname, routerData)
        , baseRouteChecked = checkpass(routerData, pathname)
        , actualAuthority = getRouteAuthority(pathname, routerMap)
        , actualRouteChecked = checkpass(routerMap, pathname);
      return (
        <Authorized
          authority={baseRouteChecked ? baseAuthority : ['notMacth']}
          noMatch={isLogin
            ? <Exception404 />
            : <Redirect to="/user/login" />
          }
        >
          <Authorized
            authority={actualRouteChecked ? actualAuthority : ['notMacth']}
            noMatch={<Exception403 />}
          >
            {children}
          </Authorized>
        </Authorized>
      );
    }
    export default connect(({ menu }) => ({
      routerData: menu.routerData,
      routerMap: menu.routerMap,
    }))(AuthComponent);
    

    但是3.x的写法会导致出现404或者403时会全屏,所以如果是局部应用的话,可以将登录校验、404/403校验分离,将 Routes 分别挂在对应的路由层级上

    相关文章

      网友评论

        本文标题:antd pro 动态菜单与动态路由

        本文链接:https://www.haomeiwen.com/subject/xowhnqtx.html