瀏覽代碼

实现按钮根据路由权限实现动态显示隐藏

祁鹏飞 5 天之前
父節點
當前提交
2a437ecc1c
共有 7 個文件被更改,包括 359 次插入273 次删除
  1. 1 0
      src/main.ts
  2. 119 107
      src/router/guards.ts
  3. 0 1
      src/router/index.ts
  4. 195 155
      src/store/modules/route.ts
  5. 13 8
      src/utils/directive.ts
  6. 27 0
      src/utils/permission.ts
  7. 4 2
      src/views/operationManagement/operation.vue

+ 1 - 0
src/main.ts

@@ -27,6 +27,7 @@ import VueCropper from "vue-cropper";
 import "vue-cropper/dist/index.css";
 
 const app = createApp(App)
+
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
   app.component(key, component)
 }

+ 119 - 107
src/router/guards.ts

@@ -1,219 +1,231 @@
-import type { Router, RouteRecordRaw } from 'vue-router'
-import useKeepAliveStore from '@/store/modules/keepAlive'
-import useMenuStore from '@/store/modules/menu'
-import useRouteStore from '@/store/modules/route'
-import useSettingsStore from '@/store/modules/settings'
-import useUserStore from '@/store/modules/user'
-import { useNProgress } from '@vueuse/integrations/useNProgress'
-import { asyncRoutes, asyncRoutesByFilesystem } from './routes'
-import '@/assets/styles/nprogress.css'
+import type { Router, RouteRecordRaw } from "vue-router";
+import useKeepAliveStore from "@/store/modules/keepAlive";
+import useMenuStore from "@/store/modules/menu";
+import useRouteStore from "@/store/modules/route";
+import useSettingsStore from "@/store/modules/settings";
+import useUserStore from "@/store/modules/user";
+import { useNProgress } from "@vueuse/integrations/useNProgress";
+import { asyncRoutes, asyncRoutesByFilesystem } from "./routes";
+import "@/assets/styles/nprogress.css";
 
 function setupRoutes(router: Router) {
   router.beforeEach(async (to, _from, next) => {
-    const settingsStore = useSettingsStore()
-    const userStore = useUserStore()
-    const routeStore = useRouteStore()
-    const menuStore = useMenuStore()
+    const settingsStore = useSettingsStore();
+    const userStore = useUserStore();
+    const routeStore = useRouteStore();
+    const menuStore = useMenuStore();
     // 是否已登录
     // console.log(556)
     if (userStore.isLogin) {
       // 是否已根据权限动态生成并注册路由
       if (routeStore.isGenerate) {
         // 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
-        settingsStore.settings.menu.mode !== 'single' && menuStore.setActived(to.path)
+        settingsStore.settings.menu.mode !== "single" &&
+          menuStore.setActived(to.path);
         // 如果已登录状态下,进入登录页会强制跳转到主页
-        if (to.name === 'login') {
+        if (to.name === "login") {
           next({
             path: settingsStore.settings.home.fullPath,
             replace: true,
-          })
+          });
         }
         // 如果未开启主页,但进入的是主页,则会进入侧边栏导航第一个模块
-        else if (!settingsStore.settings.home.enable && to.fullPath === settingsStore.settings.home.fullPath) {
+        else if (
+          !settingsStore.settings.home.enable &&
+          to.fullPath === settingsStore.settings.home.fullPath
+        ) {
           if (menuStore.sidebarMenus.length > 0) {
             next({
               path: menuStore.sidebarMenusFirstDeepestPath,
               replace: true,
-            })
+            });
           }
           // 如果侧边栏导航第一个模块均无法命中,则还是进入主页
           else {
-            next()
+            next();
           }
         }
         // 正常访问页面
         else {
-          next()
+          next();
         }
-      }
-      else {
+      } else {
         // 获取用户权限
-        settingsStore.settings.app.enablePermission && await userStore.getPermissions()
+        settingsStore.settings.app.enablePermission &&
+          (await userStore.getPermissions());
         // 生成动态路由
         switch (settingsStore.settings.app.routeBaseOn) {
-          case 'frontend':
-            routeStore.generateRoutesAtFront(asyncRoutes)
-            break
-          case 'backend':
-            await routeStore.generateRoutesAtBack()
-            break
-          case 'filesystem':
-            routeStore.generateRoutesAtFilesystem(asyncRoutesByFilesystem)
+          case "frontend":
+            routeStore.generateRoutesAtFront(asyncRoutes);
+            break;
+          case "backend":
+            await routeStore.generateRoutesAtBack();
+            break;
+          case "filesystem":
+            routeStore.generateRoutesAtFilesystem(asyncRoutesByFilesystem);
             // 文件系统生成的路由,需要手动生成导航数据
             switch (settingsStore.settings.menu.baseOn) {
-              case 'frontend':
-                menuStore.generateMenusAtFront()
-                break
-              case 'backend':
-                await menuStore.generateMenusAtBack()
-                break
+              case "frontend":
+                menuStore.generateMenusAtFront();
+                break;
+              case "backend":
+                await menuStore.generateMenusAtBack();
+                break;
             }
-            break
+            break;
         }
         // 注册并记录路由数据
         // 记录的数据会在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
-        const removeRoutes: (() => void)[] = []
+        const removeRoutes: (() => void)[] = [];
         routeStore.flatRoutes.forEach((route) => {
-
           if (!/^(?:https?:|mailto:|tel:)/.test(route.path)) {
-
-            removeRoutes.push(router.addRoute(route as RouteRecordRaw))
+            removeRoutes.push(router.addRoute(route as RouteRecordRaw));
           }
-        })
-        if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
-
+        });
+        if (settingsStore.settings.app.routeBaseOn !== "filesystem") {
           routeStore.flatSystemRoutes.forEach((route) => {
-            removeRoutes.push(router.addRoute(route as RouteRecordRaw))
-          })
+            removeRoutes.push(router.addRoute(route as RouteRecordRaw));
+          });
         }
-        routeStore.setCurrentRemoveRoutes(removeRoutes)
+        routeStore.setCurrentRemoveRoutes(removeRoutes);
         // 动态路由生成并注册后,重新进入当前路由
-        if(localStorage.getItem("token")!=''){
+        if (localStorage.getItem("token") != "") {
           next({
             path: to.path,
             query: to.query,
             replace: true,
-          })
-        }else{
-          next()
+          });
+        } else {
+          next();
         }
-
       }
-    }
-    else {
-      if (to.name !== 'login') {
+    } else {
+      if (to.name !== "login") {
         next({
-          name: 'login',
+          name: "login",
           query: {
-            redirect: to.fullPath !== settingsStore.settings.home.fullPath ? to.fullPath : undefined,
+            redirect:
+              to.fullPath !== settingsStore.settings.home.fullPath
+                ? to.fullPath
+                : undefined,
           },
-        })
-      }
-      else {
-        next()
+        });
+      } else {
+        next();
       }
     }
-  })
+  });
+  router.onError((error) => {
+    console.log("🚀 ~ setupRoutes ~ error:", error);
+    // 这里会捕获所有路由导航过程中抛出的错误
+    if (error.message && error.message.includes("does not exist")) {
+      // 上报到日志系统或 Sentry 等
+      // console.error("[路由错误] 未注册的路由名称:", error);
+      // 可以在此做统一的降级操作,但注意避免死循环
+    }
+  });
 }
 
 // 进度条
 function setupProgress(router: Router) {
-  const { isLoading } = useNProgress()
+  const { isLoading } = useNProgress();
   router.beforeEach((_to, _from, next) => {
-    const settingsStore = useSettingsStore()
+    const settingsStore = useSettingsStore();
     if (settingsStore.settings.app.enableProgress) {
-      isLoading.value = true
+      isLoading.value = true;
     }
-    next()
-  })
+    next();
+  });
   router.afterEach(() => {
-    const settingsStore = useSettingsStore()
+    const settingsStore = useSettingsStore();
     if (settingsStore.settings.app.enableProgress) {
-      isLoading.value = false
+      isLoading.value = false;
     }
-  })
+  });
 }
 
 // 标题
 function setupTitle(router: Router) {
   router.afterEach((to) => {
-    const settingsStore = useSettingsStore()
-    if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
-      settingsStore.setTitle(to.meta.breadcrumbNeste?.at(-1)?.title ?? to.meta.title)
+    const settingsStore = useSettingsStore();
+    if (settingsStore.settings.app.routeBaseOn !== "filesystem") {
+      settingsStore.setTitle(
+        to.meta.breadcrumbNeste?.at(-1)?.title ?? to.meta.title
+      );
+    } else {
+      settingsStore.setTitle(to.meta.title);
     }
-    else {
-      settingsStore.setTitle(to.meta.title)
-    }
-  })
+  });
 }
 
 // 页面缓存
 function setupKeepAlive(router: Router) {
   router.afterEach((to, from) => {
-    const keepAliveStore = useKeepAliveStore()
+    const keepAliveStore = useKeepAliveStore();
     // 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态
     if (to.meta.cache) {
-      const componentName = to.matched.at(-1)?.path.split('/').pop()
+      const componentName = to.matched.at(-1)?.path.split("/").pop();
       if (componentName) {
-        keepAliveStore.add(componentName)
-      }
-      else {
+        keepAliveStore.add(componentName);
+      } else {
         // turbo-console-disable-next-line
-        console.warn('[Fantastic-admin] 该页面组件未设置组件名,会导致缓存失效,请检查')
+        console.warn(
+          "[Fantastic-admin] 该页面组件未设置组件名,会导致缓存失效,请检查"
+        );
       }
     }
     // 判断离开页面是否开启缓存,如果开启,则根据缓存规则判断是否需要清空 keep-alive 全局状态里离开页面的 name 信息
     if (from.meta.cache) {
-      const componentName = from.matched.at(-1)?.components?.default.name
+      const componentName = from.matched.at(-1)?.components?.default.name;
       if (componentName) {
         // 通过 meta.cache 判断针对哪些页面进行缓存
         switch (typeof from.meta.cache) {
-          case 'string':
+          case "string":
             if (from.meta.cache !== to.name) {
-              keepAliveStore.remove(componentName)
+              keepAliveStore.remove(componentName);
             }
-            break
-          case 'object':
+            break;
+          case "object":
             if (!from.meta.cache.includes(to.name as string)) {
-              keepAliveStore.remove(componentName)
+              keepAliveStore.remove(componentName);
             }
-            break
+            break;
         }
         // 通过 meta.noCache 判断针对哪些页面不需要进行缓存
         if (from.meta.noCache) {
           switch (typeof from.meta.noCache) {
-            case 'string':
+            case "string":
               if (from.meta.noCache === to.name) {
-                keepAliveStore.remove(componentName)
+                keepAliveStore.remove(componentName);
               }
-              break
-            case 'object':
+              break;
+            case "object":
               if (from.meta.noCache.includes(to.name as string)) {
-                keepAliveStore.remove(componentName)
+                keepAliveStore.remove(componentName);
               }
-              break
+              break;
           }
         }
         // 如果进入的是 reload 页面,则也将离开页面的缓存清空
-        if (to.name === 'reload') {
-          keepAliveStore.remove(componentName)
+        if (to.name === "reload") {
+          keepAliveStore.remove(componentName);
         }
       }
     }
-  })
+  });
 }
 
 // 其他
 function setupOther(router: Router) {
   router.afterEach(() => {
-    document.documentElement.scrollTop = 0
-  })
+    document.documentElement.scrollTop = 0;
+  });
 }
 
 export default function setupGuards(router: Router) {
-  setupRoutes(router)
-  setupProgress(router)
-  setupTitle(router)
-  setupKeepAlive(router)
-  setupOther(router)
+  setupRoutes(router);
+  setupProgress(router);
+  setupTitle(router);
+  setupKeepAlive(router);
+  setupOther(router);
 }

+ 0 - 1
src/router/index.ts

@@ -11,7 +11,6 @@ const router = createRouter({
   history: createWebHashHistory(),
   routes: useSettingsStore(pinia).settings.app.routeBaseOn === 'filesystem' ? constantRoutesByFilesystem : constantRoutes,
 })
-// console.log(router)
 
 setupGuards(router)
 

+ 195 - 155
src/store/modules/route.ts

@@ -1,153 +1,173 @@
-import type { Route } from '#/global'
-import type { RouteRecordRaw } from 'vue-router'
-import { systemRoutes } from '@/router/routes'
-import { resolveRoutePath } from '@/utils'
-import useUserStore from '@/store/modules/user'
-import { cloneDeep } from 'lodash-es'
-import useSettingsStore from './settings'
-import api from '@/api'
-import { ElMessage } from 'element-plus'
+import type { Route } from "#/global";
+import type { RouteRecordRaw } from "vue-router";
+import { systemRoutes } from "@/router/routes";
+import { resolveRoutePath } from "@/utils";
+import useUserStore from "@/store/modules/user";
+import { cloneDeep } from "lodash-es";
+import useSettingsStore from "./settings";
+import api from "@/api";
+import { ElMessage } from "element-plus";
 const useRouteStore = defineStore(
   // 唯一ID
-  'route',
+  "route",
   () => {
-    const settingsStore = useSettingsStore()
+    const settingsStore = useSettingsStore();
 
-    const isGenerate = ref(false)
-    const routesRaw = ref<Route.recordMainRaw[]>([])
-    const filesystemRoutesRaw = ref<RouteRecordRaw[]>([])
-    const currentRemoveRoutes = ref<(() => void)[]>([])
+    const isGenerate = ref(false);
+    const routesRaw = ref<Route.recordMainRaw[]>([]);
+    const filesystemRoutesRaw = ref<RouteRecordRaw[]>([]);
+    const currentRemoveRoutes = ref<(() => void)[]>([]);
 
     // 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平
     function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T {
       if (route.children) {
-        route.children = flatAsyncRoutesRecursive(route.children, [{
-          path: route.path,
-          title: route.meta?.title,
-          icon: route.meta?.icon,
-          hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
-        }], route.path)
+        route.children = flatAsyncRoutesRecursive(
+          route.children,
+          [
+            {
+              path: route.path,
+              title: route.meta?.title,
+              icon: route.meta?.icon,
+              hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
+            },
+          ],
+          route.path
+        );
       }
-      return route
+      return route;
     }
-    function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = ''): RouteRecordRaw[] {
-      const res: RouteRecordRaw[] = []
+    function flatAsyncRoutesRecursive(
+      routes: RouteRecordRaw[],
+      breadcrumb: Route.breadcrumb[] = [],
+      baseUrl = ""
+    ): RouteRecordRaw[] {
+      const res: RouteRecordRaw[] = [];
       routes.forEach((route) => {
         if (route.children) {
-          const childrenBaseUrl = resolveRoutePath(baseUrl, route.path)
-          const tmpBreadcrumb = cloneDeep(breadcrumb)
+          const childrenBaseUrl = resolveRoutePath(baseUrl, route.path);
+          const tmpBreadcrumb = cloneDeep(breadcrumb);
           tmpBreadcrumb.push({
             path: childrenBaseUrl,
             title: route.meta?.title,
             icon: route.meta?.icon,
             hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
-          })
-          const tmpRoute = cloneDeep(route)
-          tmpRoute.path = childrenBaseUrl
+          });
+          const tmpRoute = cloneDeep(route);
+          tmpRoute.path = childrenBaseUrl;
           if (!tmpRoute.meta) {
-            tmpRoute.meta = {}
+            tmpRoute.meta = {};
           }
-          tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
-          delete tmpRoute.children
-          res.push(tmpRoute)
-          const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl)
+          tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb;
+          delete tmpRoute.children;
+          res.push(tmpRoute);
+          const childrenRoutes = flatAsyncRoutesRecursive(
+            route.children,
+            tmpBreadcrumb,
+            childrenBaseUrl
+          );
           childrenRoutes.forEach((item) => {
             // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复
-            if (res.some(v => v.path === item.path)) {
+            if (res.some((v) => v.path === item.path)) {
               res.forEach((v, i) => {
                 if (v.path === item.path) {
-                  res[i] = item
+                  res[i] = item;
                 }
-              })
+              });
+            } else {
+              res.push(item);
             }
-            else {
-              res.push(item)
-            }
-          })
-        }
-        else {
-          const tmpRoute = cloneDeep(route)
-          tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path)
+          });
+        } else {
+          const tmpRoute = cloneDeep(route);
+          tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path);
           // 处理面包屑导航
-          const tmpBreadcrumb = cloneDeep(breadcrumb)
+          const tmpBreadcrumb = cloneDeep(breadcrumb);
           tmpBreadcrumb.push({
             path: tmpRoute.path,
             title: tmpRoute.meta?.title,
             icon: tmpRoute.meta?.icon,
-            hide: !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false,
-          })
+            hide:
+              !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false,
+          });
           if (!tmpRoute.meta) {
-            tmpRoute.meta = {}
+            tmpRoute.meta = {};
           }
-          tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
-          res.push(tmpRoute)
+          tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb;
+          res.push(tmpRoute);
         }
-      })
-      return res
+      });
+      return res;
     }
     // 扁平化路由(将三级及以上路由数据拍平成二级)
     const flatRoutes = computed(() => {
-      const returnRoutes: RouteRecordRaw[] = []
-      if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
+      const returnRoutes: RouteRecordRaw[] = [];
+      if (settingsStore.settings.app.routeBaseOn !== "filesystem") {
         if (routesRaw.value) {
           routesRaw.value.forEach((item) => {
-            const tmpRoutes = cloneDeep(item.children) as RouteRecordRaw[]
+            const tmpRoutes = cloneDeep(item.children) as RouteRecordRaw[];
             tmpRoutes.map((v) => {
               if (!v.meta) {
-                v.meta = {}
+                v.meta = {};
               }
-              v.meta.auth = item.meta?.auth ?? v.meta?.auth
-              return v
-            })
-            returnRoutes.push(...tmpRoutes)
-          })
-          returnRoutes.forEach(item => flatAsyncRoutes(item))
+              v.meta.auth = item.meta?.auth ?? v.meta?.auth;
+              return v;
+            });
+            returnRoutes.push(...tmpRoutes);
+          });
+          returnRoutes.forEach((item) => flatAsyncRoutes(item));
         }
+      } else {
+        returnRoutes.push(
+          ...(cloneDeep(filesystemRoutesRaw.value) as RouteRecordRaw[])
+        );
       }
-      else {
-        returnRoutes.push(...cloneDeep(filesystemRoutesRaw.value) as RouteRecordRaw[])
-      }
-      return returnRoutes
-    })
+      return returnRoutes;
+    });
     const flatSystemRoutes = computed(() => {
-      const routes = [...systemRoutes]
-      routes.forEach(item => flatAsyncRoutes(item))
-      return routes
-    })
+      const routes = [...systemRoutes];
+      routes.forEach((item) => flatAsyncRoutes(item));
+      return routes;
+    });
 
     // TODO 将设置 meta.sidebar 的属性转换成 meta.menu ,过渡处理,未来将被弃用
-    let isUsedDeprecatedAttribute = false
-    function converDeprecatedAttribute<T extends Route.recordMainRaw[]>(routes: T): T {
+    let isUsedDeprecatedAttribute = false;
+    function converDeprecatedAttribute<T extends Route.recordMainRaw[]>(
+      routes: T
+    ): T {
       routes.forEach((route) => {
-        route.children = converDeprecatedAttributeRecursive(route.children)
-      })
+        route.children = converDeprecatedAttributeRecursive(route.children);
+      });
       if (isUsedDeprecatedAttribute) {
         // turbo-console-disable-next-line
-        console.warn('[Fantastic-admin] 路由配置中的 "sidebar" 属性即将被弃用, 请尽快替换为 "menu" 属性')
+        console.warn(
+          '[Fantastic-admin] 路由配置中的 "sidebar" 属性即将被弃用, 请尽快替换为 "menu" 属性'
+        );
       }
-      return routes
+      return routes;
     }
     function converDeprecatedAttributeRecursive(routes: RouteRecordRaw[]) {
       if (routes) {
         routes.forEach((route) => {
-          if (typeof route.meta?.sidebar === 'boolean') {
-            isUsedDeprecatedAttribute = true
-            route.meta.menu = route.meta.sidebar
-            delete route.meta.sidebar
+          if (typeof route.meta?.sidebar === "boolean") {
+            isUsedDeprecatedAttribute = true;
+            route.meta.menu = route.meta.sidebar;
+            delete route.meta.sidebar;
           }
           if (route.children) {
-            converDeprecatedAttributeRecursive(route.children)
+            converDeprecatedAttributeRecursive(route.children);
           }
-        })
+        });
       }
-      return routes
+      return routes;
     }
 
     // 生成路由(前端生成)
     function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) {
       // 设置 routes 数据
-      routesRaw.value = converDeprecatedAttribute(cloneDeep(asyncRoutes) as any)
-      isGenerate.value = true
+      routesRaw.value = converDeprecatedAttribute(
+        cloneDeep(asyncRoutes) as any
+      );
+      isGenerate.value = true;
     }
     // 格式化后端路由数据
     // function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] {
@@ -171,86 +191,106 @@ const useRouteStore = defineStore(
     //   })
     // }
     // 格式化后端路由数据
-function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] {
-  // 需要从外层移动到meta对象的字段列表
-  const metaFields = ['title', 'icon', 'auth', 'menu', 'breadcrumb', 'activeMenu', 'cache', 'noCache', 'link']// 从外层移动到meta对象的字段列表
-  return routes.filter((route: any) => route.type != 2).map((route: any) => {
-    // 处理组件引用
-    switch (route.component) {
-      case 'Layout':
-        route.component = () => import('@/layouts/index.vue')
-        break
-        default:
-          if (route.component) {
-          route.component = views[`../../views${route.component}.vue`]
-          if(route.keepAlive==='1') route.cache=true
-          if (route.menu ==0) {
-            route.menu = false;
+    function formatBackRoutes(
+      routes: any,
+      views = import.meta.glob("../../views/**/*.vue")
+    ): Route.recordMainRaw[] {
+      // 需要从外层移动到meta对象的字段列表
+      const metaFields = [
+        "title",
+        "icon",
+        "auth",
+        "menu",
+        "breadcrumb",
+        "activeMenu",
+        "cache",
+        "noCache",
+        "link",
+      ]; // 从外层移动到meta对象的字段列表
+      return routes
+        .filter((route: any) => route.type != 2)
+        .map((route: any) => {
+          // 处理组件引用
+          switch (route.component) {
+            case "Layout":
+              route.component = () => import("@/layouts/index.vue");
+              break;
+            default:
+              if (route.component) {
+                route.component = views[`../../views${route.component}.vue`];
+                if (route.keepAlive === "1") route.cache = true;
+                if (route.menu == 0) {
+                  route.menu = false;
+                }
+              } else {
+                delete route.component;
+              }
+          }
+          // 处理元数据格式转换:将指定字段从外层移动到meta对象中
+          const hasMetaFields = metaFields.some(
+            (field) => route[field] !== undefined
+          );
+          if (hasMetaFields) {
+            // 确保meta对象存在
+            if (!route.meta) {
+              route.meta = {};
+            }
+            // 批量移动字段
+            metaFields.forEach((field) => {
+              if (route[field] !== undefined) {
+                route.meta[field] = route[field];
+                delete route[field];
+              }
+            });
           }
-        }
-        else {
-          delete route.component
-        }
-    }
-    // 处理元数据格式转换:将指定字段从外层移动到meta对象中
-    const hasMetaFields = metaFields.some(field => route[field] !== undefined)
-    if (hasMetaFields) {
-      // 确保meta对象存在
-      if (!route.meta) {
-        route.meta = {}
-      }
-      // 批量移动字段
-      metaFields.forEach(field => {
-        if (route[field] !== undefined) {
-          route.meta[field] = route[field]
-          delete route[field]
-        }
-      })
-    }
 
-    // 递归处理子路由
-    if (route.children) {
-      route.children = formatBackRoutes(route.children, views)
-    }
+          // 递归处理子路由
+          if (route.children) {
+            route.children = formatBackRoutes(route.children, views);
+          }
 
-    return route
-  })
-}
+          return route;
+        });
+    }
     // 生成路由(后端获取)
     async function generateRoutesAtBack() {
-      await api.menuApi.getRoleMenuTree().then((res) => {
-        if(res.data.length > 0) {
-          // 设置 routes 数据
-          routesRaw.value = converDeprecatedAttribute(formatBackRoutes(res.data) as any)
-          isGenerate.value = true
-        }else{
-          // 退出登录
-          useUserStore().logout()
-          // 路由获取失败
-          ElMessage.error('路由获取失败')
-        }
-
-      }).catch(() => {})
+      await api.menuApi
+        .getRoleMenuTree()
+        .then((res) => {
+          if (res.data.length > 0) {
+            // 设置 routes 数据
+            routesRaw.value = converDeprecatedAttribute(
+              formatBackRoutes(res.data) as any
+            );
+            isGenerate.value = true;
+          } else {
+            // 退出登录
+            useUserStore().logout();
+            // 路由获取失败
+            ElMessage.error("路由获取失败");
+          }
+        })
+        .catch(() => {});
     }
     // 生成路由(文件系统生成)
     function generateRoutesAtFilesystem(asyncRoutes: RouteRecordRaw[]) {
       // 设置 routes 数据
-      filesystemRoutesRaw.value = cloneDeep(asyncRoutes) as any
-      isGenerate.value = true
+      filesystemRoutesRaw.value = cloneDeep(asyncRoutes) as any;
+      isGenerate.value = true;
     }
     // 记录 accessRoutes 路由,用于登出时删除路由
     function setCurrentRemoveRoutes(routes: (() => void)[]) {
-      currentRemoveRoutes.value = routes
+      currentRemoveRoutes.value = routes;
     }
     // 清空动态路由
     function removeRoutes() {
-      isGenerate.value = false
-      routesRaw.value = []
-      filesystemRoutesRaw.value = []
+      isGenerate.value = false;
+      routesRaw.value = [];
+      filesystemRoutesRaw.value = [];
       currentRemoveRoutes.value.forEach((removeRoute) => {
-        removeRoute()
-      })
-      currentRemoveRoutes.value = []
+        removeRoute();
+      });
+      currentRemoveRoutes.value = [];
     }
 
     return {
@@ -264,8 +304,8 @@ function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/
       generateRoutesAtFilesystem,
       setCurrentRemoveRoutes,
       removeRoutes,
-    }
-  },
-)
+    };
+  }
+);
 
-export default useRouteStore
+export default useRouteStore;

+ 13 - 8
src/utils/directive.ts

@@ -1,12 +1,17 @@
-import type { App, DirectiveBinding } from 'vue'
+import type { App, DirectiveBinding } from "vue";
 
+import { permission } from "./permission";
 export default function directive(app: App) {
-  app.directive('auth', (el: HTMLElement, binding: DirectiveBinding) => {
-    if (binding.modifiers.all ? useAuth().authAll(binding.value) : useAuth().auth(binding.value)) {
-      el.style.display = ''
+  app.directive("auth", (el: HTMLElement, binding: DirectiveBinding) => {
+    if (
+      binding.modifiers.all
+        ? useAuth().authAll(binding.value)
+        : useAuth().auth(binding.value)
+    ) {
+      el.style.display = "";
+    } else {
+      el.style.display = "none";
     }
-    else {
-      el.style.display = 'none'
-    }
-  })
+  });
+  app.directive("permission", permission);
 }

+ 27 - 0
src/utils/permission.ts

@@ -0,0 +1,27 @@
+import uesRoute from "@/store/modules/route";
+
+const { routesRaw } = uesRoute();
+const hasPermission = (perm) => {
+  let bool = false;
+  const hasChild = (list) => {
+    if (list.some((v) => v.name === perm)) bool = true;
+    if (list.children?.length) hasChild(list.children);
+  };
+  hasChild(routesRaw);
+};
+
+export const permission = {
+  mounted(el, binding) {
+    const { value } = binding;
+    // 如果传入的是数组,检查是否有任意一个权限
+    const hasPerm = Array.isArray(value)
+      ? value.some((perm) => hasPermission(perm))
+      : hasPermission(value);
+
+    if (!hasPerm) {
+      // 移除元素(或隐藏)
+      el.parentNode?.removeChild(el);
+      // 或者 el.style.display = 'none'
+    }
+  },
+};

+ 4 - 2
src/views/operationManagement/operation.vue

@@ -82,7 +82,7 @@
           <el-option key="cur" value="cur" label="选中本页数据"></el-option>
         </el-select>
       </span>
-      <div class="col-108 m-r-8">
+      <div class="col-108 m-r-8" v-permission="['Addoperation']">
         <el-button class="radius-2px width-100" type="primary" icon="plus" @click="addNumber">添加成员</el-button>
       </div>
       <span class="col-108 m-r-8">
@@ -139,7 +139,9 @@
               <a style="color: #409EFF; cursor: pointer">更多</a>
               <template #dropdown>
                 <el-dropdown-menu>
-                  <el-dropdown-item command="编辑">编辑</el-dropdown-item>
+                  <div v-permission="['Addoperation']">
+                    <el-dropdown-item command="编辑">编辑</el-dropdown-item>
+                  </div>
                   <el-dropdown-item command="启用">启用</el-dropdown-item>
                   <el-dropdown-item command="禁用">禁用</el-dropdown-item>
                   <el-dropdown-item command="删除">删除</el-dropdown-item>