(十六)Vue3-huohuo-admin 菜单栏

337 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天

菜单栏

在上一篇,我们详细介绍了路由及路由表,根据用户的角色权限我们划分出一份份属于当前登录用户的可访问路由。而作为后台管理系统的核心部分——菜单栏,它的生成与这份可访问路由表息息相关。路由表决定了菜单选项(有哪些菜单),路由的配置项决定了菜单选项的特征(如菜单选项的标题、图标、排序等)。

整体设计思路

image-20211229135726709

菜单栏的生成

在上篇,我们说到了路由表的生成,核心就是initRouter方法,这里我们再给出:

// 初始化路由 src\router\utils.ts
function initRouter(name: string) {
  return new Promise((resolve) => {
    getAsyncRoutes({ name }).then(({ info }) => {
      if (info.length === 0) {
        usePermissionStoreHook().changeSetting(info);
      } else {
        // 过滤后端传来的动态路由 重新生成规范路由。并将多级嵌套路由处理成一维数组
        formatFlatteningRoutes(addAsyncRoutes(info)).map(
          (v: RouteRecordRaw) => {
            // 防止重复添加路由
            if (
              router.options.routes[0].children.findIndex(
                (value) => value.path === v.path
              ) !== -1
            ) {
              return;
            } else {
              // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转
              router.options.routes[0].children.push(v);
              // 最终路由进行升序
              ascending(router.options.routes[0].children);
              if (!router.hasRoute(v?.name)) router.addRoute(v);
              const flattenRouters = router
                .getRoutes()
                .find((n) => n.path === "/");
              router.addRoute(flattenRouters);
            }
            resolve(router);
          }
        );
        usePermissionStoreHook().changeSetting(info);
      }
      router.addRoute({
        path: "/:pathMatch(.*)",
        redirect: "/error/404",
      });
    });
  });
}

initRouter返回一个Promise,这个Promise的成功返回由一个请求接口getAsyncRoutes包裹。也就是说,这里必须有一个后端接口的支持,并且返回合适的info(参考 mock 的路由表),才会顺利执行后面的代码,否则你可能会发现,成功登录后跳转的页面不会出现菜单栏。

而项目的菜单栏是在哪里控制的呢?

usePermissionStoreHook

src\store\modules\permission.ts文件中,封装了权限相关的数据 hook。想快速知道这个 hook 是为什么服务的(也就是管理哪些数据的状态),我们可以从state选项入手:

  state: () => ({
    // 静态路由生成的菜单
    constantMenus,
    // 整体路由生成的菜单(静态、动态)
    wholeMenus: [],
    // 深拷贝一个菜单树,与导航菜单不突出
    menusTree: [],
    buttonAuth: [],
    // 缓存页面keepAlive
    cachePageList: []
  }),

可以一目了然,在这里,存放了我们页面的菜单及相关的配置。所以说,语义化的命名和必要的注释是一份优秀代码必不可少的部分。

然后我们在initRouter中调用了该 hook 下的action,也就是changeSetting方法,将路由表与菜单正式衔接起来,这样我们的页面就会有菜单栏啦!

image-20220609181740209

这里提一下constantMenus

image-20220609182424716

到这里我们就完成了菜单栏的数据生成啦!

如果我不需要动态路由菜单

通过上面的代码解析,我想你应该能够知道,我不需要后端传给我的路由,所以我只需要跟后端路由无关的constantMenus就行了,将有关动态路由的额外部分都剔除即可(因为这部分都是在静态路由菜单后面添加上去的)。

根据代码逻辑,我们修改initRouter即可

function initRouter(name: string) {
  return new Promise((resolve) => {
    usePermissionStoreHook().changeSetting([]); // 传一个空的路由数组即可
    router.addRoute({
      path: "/:pathMatch(.*)",
      redirect: "/error/404",
    });
  });
}

生成路由不生成菜单

如果你只想路由正常生成,而这部分路由不出现在菜单上。我们只需要把该路由的showlink设置为 false 即可。

router.addRoute

addRoute

捕获所有路由或 404 Not found 路由

(导航栏)布局

其实整个项目的布局都是围绕菜单栏来布局的,目前开发了三种导航栏布局模式:左侧垂直菜单模式、顶部水平菜单模式、综合菜单模式

image-20220610154651246

先在心里有个数,布局部分的代码结构一览

image-20220610155604536

非菜单栏部分

image-20211221183318396

左侧垂直菜单模式

完整图示

我们默认的是左侧垂直菜单模式,示例如下

image-20211221162714333

image-20211221163406381

image-20211221163726611

顶部水平菜单模式

完整图示

image-20211221173754524

image-20220610162734508

综合菜单模式

完整图示

image-20220610162954524