ant-design vue递归渲染menu(权限管理)

ant-design vue递归渲染menu(权限管理)

目录

ant-design中未修改源码

        <a-menu-item key="1">
          <user-outlined />
          <span>nav 1</span>
        </a-menu-item>
        <a-menu-item key="2">
          <video-camera-outlined />
          <span>nav 2</span>
        </a-menu-item>
        <a-menu-item key="3">
          <upload-outlined />
          <span>nav 3</span>
        </a-menu-item>

修改后

<MenuItem :menus="getMenus()"></MenuItem>
  • 递归调用自身
    • 递归调用组件 menuItem,通过 :menus="item.children" 传递子菜单数据给自身,从而实现多层嵌套菜单的渲染。
  • 条件渲染
    • 通过 v-if 判断 item.children 是否存在,决定使用 a-sub-menu(有子菜单)还是 a-menu-item(无子菜单)进行渲染。
  • 使用键值
    • 通过 :key="item.path" 确保每个元素都有唯一的键值,优化渲染性能。
<template>
  <template v-for="item in menus" :key="item.path">
    <!-- 
      判断是否有子级 如果有则继续循环
      因为有子级,所以说我们继续循环的
     -->
    <a-sub-menu :key="item.path"  v-if ="item.children">
      <!-- 一级菜单名字 -->
      <template #title>
        {{ item.label }}
      </template>
      <!-- 子菜单,引用组件本身,组件的name就是组件名驼峰  -->
       <menuItem :menus="item.children"></menuItem> 
    </a-sub-menu>
    
    <template  v-else >
      <a-menu-item :key="item.path">
        <span>{{ item.label }}</span>
      </a-menu-item>
    </template>
    
  </template>
  
</template>

<script setup lang="ts">
/**
 * vue里面组件的递归其实就是组件的自调用
 */

import { defineProps } from "vue";

/**
 * 接收一个menus是当前渲染的组件数据
 */
const props = defineProps(["menus"]);
</script>

递归列表:

as Menus[]表示返回规范为Menus,及以下规范:

import { Role } from "../type";
export interface Menus {
  label: string;
  path: string;
  roles: Role[];
  children?: Menus[];
  component: () => any;
}

role表示渲染的权限

import { Menus } from "./layout.type";
export const getMenus = () => {
  return [
    {
      label: "门店管理",
      path: "/storeManage",
      roles: ["inspect", "familyBanquet"],
      children: [
        {
          label: "餐饮单位列表",
          path: "/company",
          roles: ["inspect"],
          component: () => import("./../views/company/company.vue"),
        },
        {
          label: "家宴中心",
          path: "/familyBanquet",
          roles: ["familyBanquet"],
          component: () => import("./../views/familyBanquet/familyBanquet.vue"),
        },
        {
          label: "乡厨信息",
          path: "/cookTeam",
          roles: ["familyBanquet"],
          component: () => import("./../views/cookTeam/cookTeam.vue"),
        },
      ],
    },
    {
      label: "监督检查",
      path: "/inspec",
      roles: ["inspect"],
      children: [
        {
          label: "监督检查列表",
          path: "/supervise",
          roles: ["inspect"],
          component: () => import("./../views/supervise/supervise.vue"),
        },
      ],
    },
    {
      label: "备案登记",
      path: "/register",
      roles: ["familyBanquet"],
      children: [
        {
          label: "备案登记",
          path: "/registerRecord",
          roles: ["familyBanquet"],
          component: () =>
            import("./../views/registerRecord/registerRecord.vue"),
        },
      ],
    },
    {
      label: "权限管理",
      path: "/sys",
      roles: ["admin"],
      children: [
        {
          label: "用户管理",
          path: "/user",
          roles: ["admin"],
          component: () => import("./../views/user/user.vue"),
        },
      ],
    },
  ] as Menus[];
};

添加role权限控制动态渲染

根据当前登录用户角色获取有权限的菜单

filter函数递归判断目录,若目录返回true则将目录加入数组

item.roles.some((key) => roles.includes(key as never));

some()用于测试数组中是否至少有一个元素满足提供的测试函数,返回布尔值

key表示当前role(权限),roles.includes(key as never)表示在当前目录的roles中是否包含当前权限,若包含返回true

<MenuItem :menus="getCurrentMenus(roles as never[])"></MenuItem>
export const getCurrentMenus = (roles = [], menus = getMenus()) => {
  return menus.filter((item) => {
    //递归调用
    if (item.children) {
      (item.children as any) = getCurrentMenus(roles, item.children as any);
    }
    // 当前用户角色和菜单角色作对比
    return item.roles.some((key) => roles.includes(key as never));//返回true或者false
  });
};
动态添加路由

在调用 router.addRoute 时传入 "layout",这表示将新添加的路由作为 layout 路由的子路由。也就是说,所有的动态路由将被挂载在 layout 这个父路由之下。

/**
 * 动态渲染路由
 */
export const addRoute = (currentMenus: Menus[], router: Router) => {
  //递归调用
  currentMenus.forEach((item) => {
    if (item.children) {
      addRoute(item.children, router);
    }
  //为layout添加子路由
    if (item.component as any) {
      router.addRoute("layout", {
        path: item.path,
        component: item.component,
        meta: {
          label: item.label
        }
      });
    }
  });
};

getMenus()为所有的菜单目录,通过getCurrentMenus()对所有的菜单目录进行权限筛选,然后传入addRoute()动态添加路由

const initAddRouter = () => {
  const global = JSON.parse(localStorage.getItem("global") || "{}");
  // 拿到有权限的菜单
  const currentMenus = getCurrentMenus(global.roles, getMenus());
  // 动态路由
  addRoute(currentMenus, router);
};

initAddRouter();

完整代码:

ts定义函数

import { Router } from "vue-router";
import { getMenus } from "./layout.config";
import { Menus } from "./layout.type";
import { useGlobalStore } from "./../store/global";
import { Role } from "../type";

/**
 * 根据当前登录用户角色
 * 获取有权限的菜单
 */
export const getCurrentMenus = (roles = [], menus = getMenus()) => {
  return menus.filter((item) => {
    if (item.children) {
      (item.children as any) = getCurrentMenus(roles, item.children as any);
    }

    // 当前用户角色和菜单角色作对比
    return item.roles.some((key) => roles.includes(key as never));//返回true或者false
  });
};

/**
 * 动态渲染路由
 */
export const addRoute = (currentMenus: Menus[], router: Router) => {
  currentMenus.forEach((item) => {
    if (item.children) {
      addRoute(item.children, router);
    }

    if (item.component as any) {
      router.addRoute("layout", {
        path: item.path,
        component: item.component,
        meta: {
          label: item.label
        }
      });
    }
  });
};

路由设置

import { createRouter, createWebHashHistory } from "vue-router";
import { addRoute, getCurrentMenus } from "./layout/layout.util";
import { getMenus } from "./layout/layout.config";

const routes = [
  {
    name: "layout",
    path: "/layout",
    component: () => import("./layout/layout.vue"),
  },
  {
    name: "login",
    path: "/login",
    component: () => import("./views/login/login.vue"),
  },
  // {
  //   name: "login",
  //   path: "/",
  //   component: () => import("./views/login/login.vue"),
  // },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

/**
 * 动态添加路由
 * 只要进入程序一定会执行这个文件
 *
 */
const initAddRouter = () => {
  const global = JSON.parse(localStorage.getItem("global") || "{}");

  // 拿到有权限的菜单
  const currentMenus = getCurrentMenus(global.roles, getMenus());

  // 动态路由
  addRoute(currentMenus, router);
};

initAddRouter();

/**
 * 每次跳转前触发
 */
// router.beforeEach((to, from, next) => {
//   const global = JSON.parse(localStorage.getItem("global") || "{}");

//   // 忽略token校验
//   const ignoreRouters = ["/login"];

//   // 如果没有,跳转到登录页面 且 不在白名单里
//   if (!global.token && !ignoreRouters.includes(to.path)) {
//     next("/login");
//     return
//   }

//   next();
// });

export default router;

完整逻辑梳理

登录后将服务器返回的token和toles存入pinia状态管理库store

const global = useGlobalStore(); 
const login = async () => {
  console.log(formState.value);
  const data = await API.login(formState.value);
   global.setToken(data.token);
  global.setRoles(data.roles); 
  const currentMenus = getCurrentMenus(data.roles as any, getMenus());
  addRoute(currentMenus, router);
  router.push(data.roles.includes("inspect") ? "/company" : "/familyBanquet");
};

点击跳转:

通过@click="linkPage"绑定点击事件,点击跳转到绑定路由key(在总菜单目录中定义的遍历key是path:"/")。

动态生成菜单:

将总菜单目录getMenus()传入getCurrentMenus(),该函数会根据权store状态存储库里拿到roles(权限)从总菜单中筛选出符合权限的数组,然后将筛选出的数组通过 :menus传入组件<MenuItem></MenuItem>,该组件通过递归遍历传入的数组来加载一级菜单和子菜单。

动态生成路由:

点击的是动态生成的菜单,但没有具体路由路径仍然无法跳转,所以我们要动态生成路由。

将筛选后的菜单和当前路由对象传入addRoute(筛选菜单,当前路由对象),动态添加路由。

列表主体:

<a-menu
          @click="linkPage"
          v-model:selectedKeys="selectedKeys"
          theme="dark"
          mode="inline"
        >
          <!-- 组件递归有权限的数据 -->
          <MenuItem :menus="getCurrentMenus(roles as never)"></MenuItem>
        </a-menu>

@click="linkPage":

key为子组件<MenuItem></MenuItem>设置的 :key="item.path",及getMenus()中的路由路径

const linkPage = ({ key }: { key: string }) => {
  router.push(key);
};

:menus

在主体中传入getCurrentMenus() :menus="getCurrentMenus(roles as never)",通过<MenuItem></MenuItem>中的

const props = defineProps(["menus"]);接收,将menus传入<MenuItem></MenuItem>组件

const props = defineProps(["menus"]);

<MenuItem></MenuItem>:

<template>
  <template v-for="item in menus" :key="item.path">
    <!-- 
      判断是否有子级 如果有则继续循环
      因为有子级,所以说我们继续循环的
     -->
    <a-sub-menu :key="item.path" v-if="item.children">
      <!-- 一级菜单名字 -->
      <template #title>
        {{ item.label }}
      </template>
      <!-- 子菜单,引用组件本身,组件的name就是组件名驼峰  -->
      <menuItem :menus="item.children"></menuItem>
    </a-sub-menu>

    <template v-else>
      <a-menu-item :key="item.path">
        <span>{{ item.label }}</span>
      </a-menu-item>
    </template>
  </template>
</template>

<script setup lang="ts">
/**
 * vue里面组件的递归其实就是组件的自调用
 */

import { defineProps } from "vue";

/**
 * 接收一个menus是当前渲染的组件数据
 */
const props = defineProps(["menus"]);
</script>

getCurrentMenus()

getMenus()为所有的菜单目录,传入菜单目录后将服务端返回的角色解析为js对象并传入roles

filter函数递归判断目录,若目录返回true则将目录加入数组

item.roles.some((key) => roles.includes(key as never));

some()用于测试数组中是否至少有一个元素满足提供的测试函数,返回布尔值

key表示传入的item中roles的所有值,并从中查询是否存在roles2

export const getCurrentMenus = (roles2 = [], menus = getMenus()) => {
  return menus.filter((item) => {
    if (item.children) {
      (item.children as any) = getCurrentMenus(roles2, item.children as any);
    }

    // 当前用户角色和菜单角色作对比
    console.log(item.roles);
    return item.roles.some((key) => roles2.includes(key as never));
  });
};

之后就是动态添加路由:动态添加路由

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mebius1916

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值