Vue 根据权限过滤菜单、按钮

菜单权限

store\modules\permisstion.ts -> generateRoutes()

import { RouteRecordRaw } from "vue-router";
//privateRoutes 前端配置好的JS菜单,权限控制由前端控制,【后端省事】
import { constantRoutes, privateRoutes } from "@/router";
import { store } from "@/store";
import router from "@/router";

import MenuAPI, { type RouteVO } from "@/api/system/menu"; 
import { MenuInfo } from "@/api/auth";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layout/index.vue");

const menuList = useStorage<MenuInfo[]>("menuList", []);


export const usePermissionStore = defineStore("permission", () => {
  // 储所有路由,包括静态路由和动态路由
  const routes = ref<RouteRecordRaw[]>([]);
  // 混合模式左侧菜单路由
  const mixedLayoutLeftRoutes = ref<RouteRecordRaw[]>([]);
  // 路由是否加载完成
  const isRoutesLoaded = ref(false);

  /**
   * 获取后台动态路由数据,解析并注册到全局路由
   *
   * @returns Promise<RouteRecordRaw[]> 解析后的动态路由列表
   */
  function generateRoutes() {
    return new Promise<RouteRecordRaw[]>((resolve, reject) => {
      // console.log("menuList", menuList);
      // console.log("privateRoutes", privateRoutes);
      // console.log("IsSuperAdmin", useUserStore().userInfo.IsSuperAdmin);
      // 超级管理员拥有所有权限
      if (useUserStore().userInfo.IsSuperAdmin == 1) {
        //管理员看到所有
        routes.value = [...constantRoutes, ...privateRoutes];
        isRoutesLoaded.value = true;
        resolve(privateRoutes);
        return;
      }
      // 筛选出有权限的菜单
      const filterPrivateRoutes = ref<RouteRecordRaw[]>([]);

      const menuCodeList = ref(
        menuList.value.map((item) => item.Code).filter((code) => code !== undefined)
      );
      // console.log("menuCodeList", menuCodeList);
      //过滤出有权限的菜单
      privateRoutes.forEach((item) => {
        if (Array.isArray(item.code)) {
          // 过滤出有权限的子菜单
          const authorizedChildren = item.children?.filter((child) =>
            menuCodeList.value.includes(child.code)
          );

          // 如果有权限的子菜单存在,则添加到结果中
          if (authorizedChildren && authorizedChildren.length > 0) {
            filterPrivateRoutes.value.push({
              ...item,
              children: authorizedChildren,
            });
          }
        } else {
          if (menuCodeList.value.includes(item.code)) {
            filterPrivateRoutes.value.push(item);
          }
        }
      });
      //console.log("filterPrivateRoutes", filterPrivateRoutes.value);
      routes.value = [...constantRoutes, ...filterPrivateRoutes.value];
      isRoutesLoaded.value = true;
      resolve(filterPrivateRoutes.value);
      //不从服务器获取
      // MenuAPI.getRoutes()
      //   .then((data) => {
      //     const dynamicRoutes = parseDynamicRoutes(data);
      //     routes.value = [...constantRoutes, ...dynamicRoutes, ...privateRoutes];
      //     //routes.value = [...constantRoutes, ...privateRoutes]; //去掉 dynamicRoutes 隐藏 原始菜单
      //     isRoutesLoaded.value = true;
      //     resolve(dynamicRoutes);
      //   })
      //   .catch((error) => {
      //     reject(error);
      //   });
    });
  }

按钮权限

directive\permission\index.ts

import type { Directive, DirectiveBinding } from "vue";

import { useUserStore } from "@/store";

/**
 * 按钮权限
 */
export const hasPerm: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const requiredPerms = binding.value;

    // 校验传入的权限值是否合法
    if (!requiredPerms || (typeof requiredPerms !== "string" && !Array.isArray(requiredPerms))) {
      throw new Error(
        "需要提供权限标识!例如:v-has-perm=\"'sys:user:add'\" 或 v-has-perm=\"['sys:user:add', 'sys:user:edit']\""
      );
    }

    const { IsSuperAdmin } = useUserStore().userInfo;
    const functionList = useUserStore().functionList;

    const perms = ref(functionList.map((item) => item.Code).filter((code) => code !== undefined));

    // console.log("IsSuperAdmin", IsSuperAdmin);
    // console.log("functionList", functionList);
    // console.log("perms", perms);
    // console.log("requiredPerms", requiredPerms);
    // 超级管理员拥有所有权限
    if (IsSuperAdmin == 1) {
      return;
    }

    // // 检查权限
    const hasAuth = Array.isArray(requiredPerms)
      ? requiredPerms.some((perm) => perms.value.includes(perm))
      : perms.value.includes(requiredPerms);

    // console.log("hasAuth", hasAuth);
    //如果没有权限,移除该元素
    if (!hasAuth && el.parentNode) {
      // console.log("removeChild", el);
      el.parentNode.removeChild(el);
    }
  },
};

/**
 * 角色权限指令
 */
export const hasRole: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const requiredRoles = binding.value;

    // 校验传入的角色值是否合法
    if (!requiredRoles || (typeof requiredRoles !== "string" && !Array.isArray(requiredRoles))) {
      throw new Error(
        "需要提供角色标识!例如:v-has-role=\"'ADMIN'\" 或 v-has-role=\"['ADMIN', 'TEST']\""
      );
    }

    const { roles } = useUserStore().userInfo;

    // 检查是否有对应角色权限
    const hasAuth = Array.isArray(requiredRoles)
      ? requiredRoles.some((role) => roles.includes(role))
      : roles.includes(requiredRoles);

    // 如果没有权限,移除元素
    if (!hasAuth && el.parentNode) {
      el.parentNode.removeChild(el);
    }
  },
};

路由权限
plugins\permission.ts

import type { NavigationGuardNext, RouteLocationNormalized, RouteRecordRaw } from "vue-router";
import NProgress from "@/utils/nprogress";
import { getAccessToken } from "@/utils/auth";
import router from "@/router";
import { usePermissionStore, useUserStore } from "@/store";

export function setupPermission() {
  // 白名单路由
  const whiteList = ["/login", "/datav"];
  // 公共菜单,只要登录了就能访问
  const constantPathList = ["/redirect", "/dashboard", "/401", "/404", "/profile", "/myNotice"];

  router.beforeEach(async (to, from, next) => {
    // 开始进度条
    NProgress.start();

    const isLogin = !!getAccessToken(); // 判断是否登录
    if (isLogin) {
      if (to.path === "/login") {
        // 已登录,访问登录页,跳转到首页
        next({ path: "/" });
      } else {
        const permissionStore = usePermissionStore();
        //console.log("permissionStore", permissionStore);
        // 判断路由是否加载完成
        if (permissionStore.isRoutesLoaded) {
          if (to.matched.length === 0) {
            // 路由未匹配,跳转到404
            console.log("Route 加载完成后,路由未匹配 => ", to.path);
            next("/404");
          } else if (whiteList.includes(to.path)) {
            console.log("Route 加载完成后,白名单 => ", to.path);
            next();
          } else {
            const routeExists = constantPathList.some((path) => to.path.includes(path));
            if (!routeExists) {
              //有权限的 + 白名单 = 可访问
              //console.log("Route 加载【后】 => ", to.path);
              permissionCheck(permissionStore, to, next);
            }
            // 动态设置页面标题
            const title = (to.params.title as string) || (to.query.title as string);
            if (title) {
              to.meta.title = title;
            }
            next();
          }
        } else {
          try {
            // 生成动态路由 -- 直接游览器里输入地址会走到这边
            const dynamicRoutes = await permissionStore.generateRoutes();
            const routeExists = constantPathList.some((path) => to.path.includes(path));
            if (!routeExists) {
              //有权限的 + 白名单 = 可访问
              //console.log("Route 加载【前】 => ", to.path);
              permissionCheck(permissionStore, to, next);
            }
            dynamicRoutes.forEach((route: RouteRecordRaw) => router.addRoute(route));
            next({ ...to, replace: true });
          } catch (error) {
            console.error(error);
            // 路由加载失败,重置 token 并重定向到登录页
            await useUserStore().clearUserData();
            redirectToLogin(to, next);
            NProgress.done();
          }
        }
      }
    } else {
      // 未登录,判断是否在白名单中
      if (whiteList.includes(to.path)) {
        next();
      } else {
        // 不在白名单,重定向到登录页
        redirectToLogin(to, next);
        NProgress.done();
      }
    }
  });

  // 后置守卫,保证每次路由跳转结束时关闭进度条
  router.afterEach(() => {
    NProgress.done();
  });
}

function permissionCheck(permissionStore: any, to: any, next: NavigationGuardNext) {
  //console.log("permissionStore.routes", permissionStore.routes);
  let found = false;
  permissionStore.routes.forEach((route: RouteRecordRaw) => {
    //console.log("permissionStore.route", route.path);
    if (to.path.includes(route.path) && route.path != "/") {
      //console.log("to.path => ", to.path);
      //console.log("route.path => ", route.path);
      found = true;
    }
  });
  if (!found) {
    console.log("无权 => ", to.path);
    next("/401");
  }
}

// 重定向到登录页
function redirectToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {
  const params = new URLSearchParams(to.query as Record<string, string>);
  const queryString = params.toString();
  const redirect = queryString ? `${to.path}?${queryString}` : to.path;
  next(`/login?redirect=${encodeURIComponent(redirect)}`);
}

/** 判断是否有权限 */
export function hasAuth(value: string | string[], type: "button" | "role" = "button") {
  const { roles, perms } = useUserStore().userInfo;

  // 超级管理员 拥有所有权限
  if (type === "button" && roles.includes("ROOT")) {
    return true;
  }

  const auths = type === "button" ? perms : roles;
  return typeof value === "string"
    ? auths.includes(value)
    : value.some((perm) => auths.includes(perm));
}

posted @ 2025-08-22 17:21  VipSoft  阅读(26)  评论(0)    收藏  举报