当前位置: 首页 > wzjs >正文

宁波哪里做网站软文吧

宁波哪里做网站,软文吧,网易企业邮箱怎么发送文件,工程项目管理软件系统目录 路由权限和动态路由问题解决问题实现步骤:第一步:封装调用接口 API 方法第二步:路由权限控制获取登录用户数据 后端动态路由配置表配置步骤Glob 导入注意事项:第一步、import.meta.glob的加载与匹配第二步、添加动态路由 rou…

目录

  • 路由权限和动态路由
    • 问题
    • 解决问题
    • 实现步骤:
      • 第一步:封装调用接口 API 方法
      • 第二步:路由权限控制获取登录用户数据
    • 后端动态路由配置表配置步骤
      • Glob 导入注意事项:
      • 第一步、import.meta.glob的加载与匹配
      • 第二步、添加动态路由 router.addRoute
      • 第三步:使用 Pinia 保存动态路由(菜单)数据
      • 第四步、缓存路由名称 name 值存在到 pinia
      • 第五步、修改Layout渲染出口缓存 `<keep-alive>`
      • 第六步、动态渲染左侧主菜单
      • 第七步、查看效果:
    • 按钮级权限控制:
      • 第一步:自定义权限指令(全局指令)
      • 第二步、使用自定义指令控制权限
    • 自定义权限函数
      • 第一步:定义authFunction函数权限文件
      • 第二步、使用自定义的权限函数
      • 第三步、测试使用效果

路由权限和动态路由

问题

当前系统如果未登录,可以进行访问系统功能,这样肯定需要避免的,登陆后只能获取到令牌access_token,我们还需要获取登录的用户信息,并且获取用户拥有的菜单和按钮权限

解决问题

解决问题:通过路由前置beforeEach实现路由权限控制

  1. 判断用户是否已经登录并拥有accsee_token
  2. 如果没有登陆,也就是没有accsee_token,则跳转到登录页面。
  3. 如果已登录,有accsee_token令牌,判断是否有菜单信息或者用户信息,如果有则正常页面跳转,如果没有则请求接口获取菜单及用户信息后,则进行动态路由加载route.addRoute,然后正常页面跳转。

实现步骤:

第一步:封装调用接口 API 方法

  1. src/api/auth/index.ts 文件中定义查询用户权限信息接口:
    import request from "@/utils/request";
    const baseUrl = "/auth";
    // 查询用户拥有的菜单按钮权限(动态路由)
    export function getMenuUser() {return request({url: `${baseUrl}/user`,method: 'GET',});
    }
    

第二步:路由权限控制获取登录用户数据

登录后,如果未获取到用户信息和权限信息,则发送请求获取,获取后进行pinia将数据管理起来。

  1. src/types/pinia.d.ts 完善 用户认证状态 类型
    /*** pinia状态类型定义*/
    declare interface layoutConfigState {isCollapse: boolean; // 是否展开菜单globalTitle: string; // 网站主标题isFullscreen: boolean; // 是否全屏isDark: boolean;  // 是否暗黑主题
    }
    // 登录信息
    declare interface LoginData {username: string;password: string;
    }// 认证用户信息
    declare interface UserInfo {nickName: string;username: string;imageUrl: string;
    }
    // 用户认证状态
    declare interface AuthState<T = any> {rememberData?: LoginData; // 记住我(登录数据)accessToken?: string; //访问令牌userInfo?: UserInfo; // 登录用户信息 ++++++buttonList: string[]; // 路由菜单权限 ++++++menuList: T[]; // 路由菜单权限 ++++++
    }
    
  2. src/stores/auth.ts 中管理用户信息userInfobuttonList按钮权限、menuList菜单权限
    import { defineStore } from 'pinia';
    import type { RouteRecordRaw } from 'vue-router';
    import { Session, Local } from '@/utils/storage';
    import { login, logout, getMenuUser } from '@/api/auth';
    // 保存到 Local或 Session的key名
    export const Key = {rememberKey: 'isRemember', // 记住密码的keyaccessTokenKey: 'accessToken', // 访问令牌本地保存的keyuserInfoKey: 'userInfo', // 用户信息本地保存的key
    }
    /**
    * 用户所拥有的路由权限
    */
    export const useAuthStore = defineStore('auth', {state: (): AuthState<RouteRecordRaw> => {return {rememberData: Local.get(Key.rememberKey), // 记住密码accessToken: Session.get(Key.accessTokenKey), // 访问令牌字符串userInfo: Session.get(Key.userInfoKey),buttonList: [],menuList: []}},actions: {// 更新用户信息 ++++++async setUserInfo(data: UserInfo) {this.userInfo = data;Session.set(Key.userInfoKey, data);},// 获取按钮列表async setButtonList(data: string[]) {this.buttonList = data;},// 更新菜单权限 async setMenuList(data = [] as RouteRecordRaw[]) {this.menuList = data;},// 记住密码setRememberPwd(data?: LoginData) {this.rememberData = data;if (data) {Local.set(Key.rememberKey, { username: data.username, password: data.password });} else {Local.remove(Key.rememberKey);}},// 登录操作userLogin(loginData: LoginData) {return new Promise((resolve, reject) => {login(loginData).then((res: any) => {const { data } = res;// 状态赋值const { access_token } = data;this.accessToken = access_token;// 保存到session中Session.set(Key.accessTokenKey, access_token);// 正常响应钩子resolve(res);}).catch((error: Error) => {reject(error); // 异常});});},userLogout() {return new Promise((resolve, reject) => {logout().then((res: any) => {// 重置状态this.resetUserState();// 重新加载当前页,需认证页面会去登录页window.location.reload();resolve(res);}).catch((error: Error) => {reject(error);})});},// 重置用户状态resetUserState() {this.menuList = [];this.accessToken = undefined;this.userInfo = undefined;// 移除保存的数据Session.remove(Key.accessTokenKey);Session.remove(Key.userInfoKey);}}
    });
    
  3. 创建路由权限控制文件: src/router/permission.ts ,核心使用路由前置 beforeEach 进行处理
    import router from '@/router';
    import { initBackEndRoutes } from '../router/backEnd';
    import { useAuthStore } from '@/stores/auth';
    // 白名单,无需认证可访问路由地址
    const whiteList = ['/login'] // no redirect whitelist
    // 路由前置:跳转到目标路由前被调用
    router.beforeEach(async (to, from, next) => {const authStore = useAuthStore();// 获取访问令牌const hasToken = authStore.accessToken;if (hasToken) {// 已登录,有令牌if (to.path === '/login') {return next({ path: '/' });} else {// 获取用户拥有的菜单权限信息if (!authStore.userInfo || authStore.menuList.length <= 0) {// 后端控制路由await initBackEndRoutes();// 解决刷新时:一直跳 404 页面问题return next({ path: to.path, query: to.query });} else {return next();}}} else {// 未登录,无令牌// 如果是白明单,放行if (whiteList.indexOf(to.path) !== -1) {return next();} else {// 无令牌,跳转登录页return next(`/login?redirect=${to.path}`);}}
    });
    
  4. 创建后端动态路由控制文件 src/router/backEnd.ts ,文件中定义 initBackEndRoutes 方法,方法目前实现获取用户信息和权限信息:
    import { getMenuUser } from '@/api/auth/index';import { useAuthStore } from '@/stores/auth';/*** 根据后端返回路由数据,进行动态路由控制* 1. 请求接口获取用户数据和权限数据*/export async function initBackEndRoutes() {// 1、判断用户是否登录,未登录结束const authStore = useAuthStore();if (!authStore.accessToken) return false;// 2、请求接口获取用户拥有的权限菜单(路由数据)const { data } = await getMenuUser();const { userInfo, menuList, buttonList } = data;// 3、没有任何菜单权限,返回falseif (!userInfo || !menuList || menuList.length <= 0) return false;authStore.setUserInfo(userInfo);authStore.setButtonList(buttonList);authStore.setMenuList(menuList);}
    
  5. main.ts 中引入路由权限拦截器,让其生效。
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import App from './App.vue'
    import router from './router'
    // 图标
    import { useElIcon } from '@/utils/setGlobal';
    // 引入路由拦截器
    import '@/router/permission';
    const app = createApp(App)
    app.use(createPinia())
    app.use(router)
    app.use(ElementPlus, { locale: zhCn });
    // 全局注册图标`ele-`开头(样式在index.scss中)
    useElIcon(app);
    app.mount('#app')
    
    Vue3路由权限和动态路由
    Vue3路由权限和动态路由

后端动态路由配置表配置步骤

当前路由配置表是静态定义在src/router/index.ts文件中,如果要添加路由配置则需要在此文件中添加,希望根据后端返回的用户菜单权限数据来进行动态路由配置,用户有什么菜单权限,则只动态配置对应的路由,没有的权限菜单则不配置路由,上面的步骤中,已经获取了用户拥有的菜单权限,根据获取的菜单权限动态配置到路由表中。

Glob 导入注意事项:

Glob文档直通车

  • 这只是一个 Vite 独有的功能而不是一个 WebES 标准
  • Glob 模式会被当成导入标识符:必须是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目 根目录解析)或一个别名路径(请看 resolve.alias 选项)。
  • Glob 匹配是使用 fast-glob 来实现的 —— 阅读它的文档来查阅 支持的 Glob 模式。
  • 所有 import.meta.glob 的参数都必须以字面量传入。
  • 你 不 可以在其中使用变量或表达式
  • ! 作为前缀,忽略结果中的一些文件
  • 返回结果为对象数组:[{key文件路径,value动态导入组件方法}]
    如: ['/src/views/home/index.vue', () => import("/src/views/home/index.vue")]

第一步、import.meta.glob的加载与匹配

后端返回的 menuList 菜单权限数据中的 component 属性值是字符串,则在路由配置时动态引用组件的函数,我们可以 Vite 提供的 import.meta.glob 函数加载项目中的多个文件模块,再根据 component 属性值从加载的文件模块中匹配出来。

  1. src/router/backEnd.ts 代码实现:

    import { getMenuUser } from '@/api/auth/index';
    import { useAuthStore } from '@/stores/auth';// ++++++
    import type { RouteComponent, RouteRecordRaw } from 'vue-router';
    /**
    * 获取 src/views 目录下的 .vue 全部文件,排除其 components 目录下的子组件文件
    */
    const modules: Record<string, RouteComponent> =import.meta.glob(['@/views/**/*.vue', '!@/views/**/components/**']);
    // 相对路径路由模块: 将 key本地路由组件路径 `/src/views` 或 `../views` 都替换为 ''
    const viewsModules: Record<string, RouteComponent> = Object.keys(modules).reduce((prevObj,currKey) => Object.assign(prevObj, {[currKey.replace(/\/src\/views|..\/views/, '')]:modules[currKey]}), {});console.log('viewsModules', viewsModules);
    /**
    * 根据后端返回路由数据,进行动态路由控制
    * 1. 请求接口获取用户数据和权限数据
    */
    export async function initBackEndRoutes() {
    // 省略
    }
    

    Vue3路由权限和动态路由

  2. 后端路表中 component 转换为动态导入路由组件函数

    import { dynamicRoutes, defaultRoutes } from '@/router';
    /**
    * 后端路表中 component 转换为动态导入路由组件
    * @param dynamicRoutes 后端返回的路由表数组
    * @returns component 值已转换为动态导入路由组件方法
    */
    export function dynamicImportComponent(dynamicRoutes: RouteRecordRaw[]) {if (!dynamicRoutes || dynamicRoutes.length <= 0) return;return dynamicRoutes.map((route) => {// 存在 component 值,则查找对应动态导入组件方法const { component } = route;if (component) route.component = viewsModules[`${component}`] ||viewsModules[`/${component}`];route.children && dynamicImportComponent(route.children);return route;});
    }
    

3.拆分前端路由配置表

修改 src/router/index.ts 路由静态配置表:

  1. 在 dynamicRoutes 存放可权限控制的动态路由:后端请求路由配置数据后,赋值给下面路由数组的顶级对象的children属性(即 布局 Layout 对象的children 属性)。
  2. 增加 defaultRoutes 用于配置默认路由:将所有用户都可访问的路由,不管前端控制还是后端控制路由权限,都要将下面添加到此配置中。
  3. fullscreenRoutes 全屏显示路由,不作用到 layout 布局渲染出口,且不通过后端控制其配置路由是一直存在的。
  4. 然后创建路由对象中,将 defaultRoutes fullscreenRoutes 路由配置解构合并起来:即: routes: [...defaultRoutes, ...fullscreenRoutes]

完整代码实现:

import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
/**
* 因为 Vue-Router 提供的配置路由对象的 meta 属性有限,所以须要扩展 RouteMeta 接口。
* 路由对象 meta 属性说明:
* meta: {
* title: 菜单标题
* icon: 菜单图标
* linkTo: 外链地址(新窗口打开)
* cache: 是否缓存:true缓存,false不缓存,会将 name 值用于 <keep-alive>的includes上
* hidden: 是否在菜单中显示:true显示,false隐藏
* isBreadcrumb: 是否显示到面包屑:默认或true会显示,false不显示。
* }
*/
declare module 'vue-router' {interface RouteMeta {title?: string;icon?: string;linkTo?: string;cache?: boolean;hidden?: boolean;isBreadcrumb?: boolean;}
}
/**
* 动态路由:后端请求路由配置数据后,赋值给下面路由数组的顶级对象的children属性(即 布局 Layout 对象的
children属性)
* @returns 动态路由配置数组
*/
export const dynamicRoutes: RouteRecordRaw[] = [{path: '/',name: 'Layout',component: () => import('@/layout/index.vue'),redirect: '/home',children: [{path: '/home',name: 'Home',component: () => import('@/views/home/index.vue'),meta: {title: '首页',icon: 'ele-HomeFilled',}},{path: '/system',name: 'System',redirect: '/system/menu',meta: {title: '系统管理',icon: 'ele-Setting',},children: [{path: '/system/menu',name: 'SystemMenu',component: () => import('@/views/system/menu/index.vue'),meta: {title: '菜单管理',icon: 'ele-Menu',}},{path: '/system/role',name: 'SystemRole',component: () => import('@/views/system/role/index.vue'),meta: {title: '角色管理',icon: 'ele-IceCream',}},{path: '/system/user',name: 'SystemUser',component: () => import('@/views/system/user/index.vue'),meta: {title: '用户管理',icon: 'ele-User',}}]},{path: "/goods",name: "Goods",redirect: "/goods/list",meta: {title: "商品管理",icon: "ele-Goods",cache: true,hidden: false},children: [{path: "/goods/list",name: "GoodsList",component: () => import('@/views/goods/list.vue'),meta: {title: "商品列表",icon: "ele-GoodsFilled",cache: false,hidden: false}},{path: "/goods/category",name: "GoodsCategory",component: () => import('@/views/goods/category.vue'),meta: {title: "商品分类",icon: "ele-Operation",cache: false,hidden: false}}]},{path: '/huoshankou',name: 'Mengxuegu',component: () => import('@/views/link/index.vue'),meta: {title: '火山口官网',icon: 'ele-Link',linkTo: 'https://www.atjuboyun.com' // 外链跳转地址}},]},
];
/**
* 默认路由配置,所有用户都可访问的路由,不管前端控制还是后端控制路由权限,都要将下面添加到路由表
* (后端路由控制:后端配置菜单数据中不需要下面的菜单项)
* @returns 默认路由配置数组
*/
export const defaultRoutes: RouteRecordRaw[] = [{path: '/401',name: 'NoPermission',component: () => import('@/views/error/401.vue'),meta: {title: '401页面',icon: 'ele-Warning',hidden: false,},},{path: '/:path(.*)*', // 404匹配其他路由地址name: 'NotFound',component: () => import('@/views/error/404.vue'),meta: {title: '未找到此页面',hidden: true,},},
];
/**
* 全屏显示路由,不作用到 layout 布局渲染出口。
* (后端路由控制:后端配置菜单数据中不需要下面的菜单项)
*/
export const fullscreenRoutes: RouteRecordRaw[] = [{path: '/login',name: 'Login',component: () => import('@/views/auth/login.vue'),meta: {title: '登录',hidden: true,},},
];
// 创建路由实例
export const router = createRouter({// 参数获取的是 vite.config.ts 中的 base 属性值history: createWebHashHistory(import.meta.env.BASE_URL),// 默认添加 401、404 路由配置,有 404 可防止控制台一直提示 No match found for location with path  'xxx'routes: [...defaultRoutes, ...fullscreenRoutes],
});
export default router;

第二步、添加动态路由 router.addRoute

/**
* 添加动态路由,使用 `router.addRoute`
* @link 参考:https://router.vuejs.org/zh/guide/advanced/dynamic-routing.html
*/
export function addRouteHandle() {// 1. 将默认路由 401、404 合并,防止不在 layout 布局中;不然会全屏显示401、404页面const layoutRouteChildren = [...dynamicRoutes[0].children || [], ...defaultRoutes];dynamicRoutes[0].children = layoutRouteChildren;// 2. 添加动态路由dynamicRoutes.forEach(route => {router.addRoute(route);});
}

第三步:使用 Pinia 保存动态路由(菜单)数据

将转换后的所有目标路由 使用 Pinia Store 保存下来

/**
* 将动态路由保存到pinia状态中
*/
function setDynamicRoutes() {// 存储动态路由const authStore = useAuthStore();authStore.setMenuList(dynamicRoutes[0].children);
}

第四步、缓存路由名称 name 值存在到 pinia

src/layout/layoutMain/index.vue 文件中使用 <keep-alive :includes=[xxx, xxx] />includes 属性来指定要缓存路由组件name值。而 includes 指定的是路由配置表中的 meta.name (当然此值要与 <script setup name="组件name"> name属性值相同),且配置了 meta.cache: true 才会进行缓存。
下面我们就将路由配置中的 meta.cache: truemeta.name 值获取到进行Pinia管理起来。

  1. src/types/pinia.d.ts 定义视图路由状态类型

    // 视图路由状态
    declare interface ViewRoutesState {cacheRouteNames: string[]; // 要缓存路由的name
    }
    
  2. 创建 src/stores/viewRoutes.ts 管理 要缓存的组件name

import { defineStore } from 'pinia';
/**
* 管理路由组件
*/
export const useViewRoutesStore = defineStore('viewRoutes', {state: (): ViewRoutesState => {return {cacheRouteNames: [], // 要缓存的组件name}},actions: {// 更新状态async setCacheRouteNames(data: string[]) {this.cacheRouteNames = data;},}
});
  1. src/router/backEnd.ts 添加 setCacheRouteNames 方法,获取需要缓存的路由名称 name 存在到
    pinia
    /**
    * 获取需要缓存的路由名称 `name` 存在到pinia
    * 用于:src/layout/layoutMain/index.vue 文件中的 <keep-alive :includes=[xxx, xxx] />
    */
    export function setCacheRouteNames() {// 存储缓存路由 nameconst cacheRouteNames: string[] = [];const _getNames = (route: RouteRecordRaw) => {// 获取缓存路由的name值if (route.meta?.cache && route.name) cacheRouteNames.push(route.name as string);// 有子路由,则递归获取name值if (route.children && route.children.length) {route.children.forEach(item => _getNames(item));}}_getNames(dynamicRoutes[0]);// 保存到 piniaconst viewRoutesStore = useViewRoutesStore();viewRoutesStore.setCacheRouteNames(cacheRouteNames);
    }
    

src/router/backEnd.ts 完整代码:

import { getMenuUser } from '@/api/auth/index';
import { router } from '@/router';
import { dynamicRoutes, defaultRoutes } from '@/router';
import { useAuthStore } from '@/stores/auth';
import type { RouteComponent, RouteRecordRaw } from 'vue-router';
import { useViewRoutesStore } from '@/stores/viewRoutes';
/**
* 获取 src/views 目录下的 .vue 全部文件,排除其 components 目录下的子组件文件
* import.meta.glob 参考:https://cn.vitejs.dev/guide/features.html#glob-import
*/
const modules: Record<string, RouteComponent> = import.meta.glob(['@/views/**/*.vue','!@/views/**/components/**']);
// 相对路径路由模块: 将key本地路由组件路径 `/src/views` 或 `../views` 都替换为 ''
const viewsModules: Record<string, RouteComponent> = Object.keys(modules).reduce((prevObj, currKey) => Object.assign(prevObj, {[currKey.replace(/\/src\/views|..\/views/, '')]:modules[currKey]
}), {});
// console.log('viewsModules', viewsModules);
/**
* 根据后端返回路由数据,进行动态路由控制
* 1. 请求接口获取用户数据和权限数据
*/
export async function initBackEndRoutes() {// 1、判断用户是否登录,未登录结束const authStore = useAuthStore();if (!authStore.accessToken) return false;// 2、请求接口获取用户拥有的权限菜单(路由数据)const { data } = await getMenuUser();const { userInfo, menuList, buttonList } = data;// 3、没有任何菜单权限,返回falseif (!userInfo || !menuList || menuList.length <= 0) return false;authStore.setUserInfo(userInfo);authStore.setButtonList(buttonList);// authStore.setMenuList(menuList);// 4、路由对象中 component 属性的字符串值,从 viewsModules 中查找对应的动态导入路由组件方法dynamicRoutes[0].children = dynamicImportComponent(menuList);// console.log('dynamicRoutes0', dynamicRoutes, data);// 5. 添加动态路由await addRouteHandle();// 6. 存储动态路由到 pinia storesetDynamicRoutes();// 7. 存储缓存路由名到 pinia store setCacheRouteNames();
}
/**
* 后端路表中 component 转换为动态导入路由组件
* @param dynamicRoutes 后端返回的路由表数组
* @returns component 值已转换为动态导入路由组件方法
*/
export function dynamicImportComponent(dynamicRoutes: RouteRecordRaw[]) {if (!dynamicRoutes || dynamicRoutes.length <= 0) return;return dynamicRoutes.map((route) => {// 存在 component 值,则查找对应动态导入组件方法const { component } = route;if (component) route.component = viewsModules[`${component}`] ||viewsModules[`/${component}`];route.children && dynamicImportComponent(route.children);return route;});
}
/**
* 添加动态路由,使用 `router.addRoute`
* @link 参考:https://router.vuejs.org/zh/guide/advanced/dynamic-routing.html
*/
export function addRouteHandle() {// 1. 将默认路由 401、404 合并,防止不在 layout 布局中;不然会全屏显示401、404页面const layoutRouteChildren = [...dynamicRoutes[0].children || [], ...defaultRoutes];dynamicRoutes[0].children = layoutRouteChildren;// 2. 添加动态路由// console.log('dynamicRoutes', dynamicRoutes)dynamicRoutes.forEach(route => {router.addRoute(route);});
}
/**
* 将动态路由保存到pinia状态中
*/
function setDynamicRoutes() {// 存储动态路由const authStore = useAuthStore();authStore.setMenuList(dynamicRoutes[0].children);
}
/**
* ++++++
* 获取需要缓存的路由名称 `name` 存在到pinia
* 用于:src/layout/layoutMain/index.vue 文件中的 <keep-alive :includes=[xxx, xxx] />
*/
export function setCacheRouteNames() {// 存储缓存路由nameconst cacheRouteNames: string[] = [];const _getNames = (route: RouteRecordRaw) => {// 获取缓存路由的name值if (route.meta?.cache && route.name) cacheRouteNames.push(route.name as string);// 有子路由,则递归获取name值if (route.children && route.children.length) {route.children.forEach(item => _getNames(item));}}_getNames(dynamicRoutes[0]);// 保存到 piniaconst viewRoutesStore = useViewRoutesStore();viewRoutesStore.setCacheRouteNames(cacheRouteNames);
}

第五步、修改Layout渲染出口缓存 <keep-alive>

src/layout/layoutMain/index.vue,获取 pinia 存储的 要缓存的路由组件name,然后指定到<keep-alive :include="xxx">

 <template><!-- 右侧主区域 --><el-main class="layout-main"><el-scrollbar><div class="layout-main-warp"><router-view v-slot="{ Component }"><transition name="fade-transform" mode="out-in"><keep-alive :include="cacheRouteNames"><component :is="Component"></component></keep-alive></transition></router-view></div></el-scrollbar></el-main>
</template><script setup lang="ts" name="LayoutMain">
import { useViewRoutesStore } from "../../stores/viewRoutes";
import { computed } from "vue";
const viewRoutesStore = useViewRoutesStore();
// 获取要缓存的路由组件name
const cacheRouteNames = computed(() => viewRoutesStore.cacheRouteNames);
</script><style scoped lang="scss">
:deep(.el-scrollbar__view) {/* 铺满高度 */height: 100%;
}
</style>

第六步、动态渲染左侧主菜单

  1. src/layout/layoutAside/index.vuePinia 中获取保存用户权限菜单数据 menuList ,将其数据过滤掉不显示的隐藏菜单( meta.hiddenfalse),只保留 meta.hiddentrue的数据,将过滤后的目标数据传递到子组件 VerticalMenu 中:
    <!-- <template><div class="h100"><el-asideclass="layout-container layout-aside layout-aside-menu-200":class="layoutConfig.isCollapse? 'layout-aside-menu-60': 'layout-aside-menu-200'"><logo /><VerticalMenu /></el-aside></div>
    </template> -->
    <template><!-- 左侧菜单区域 --><div class="h100"><el-asideclass="layout-aside layout-aside-menu-200":class="layoutConfig.isCollapse? 'layout-aside-menu-60': 'layout-aside-menu-200'"><!-- LOGO --><Logo /><!-- 菜单 --><VerticalMenu :menuList="state.menuList" /></el-aside></div>
    </template><script setup lang="ts" name="LayoutAside">
    import { useLayoutConfigStore } from "../../stores/layoutConfig";
    import { useAuthStore } from "../../stores/auth";
    import { defineAsyncComponent, onMounted, reactive } from "vue";
    import type { RouteRecordRaw } from "vue-router";const authStore = useAuthStore();
    const Logo = defineAsyncComponent(() => import("./logo.vue"));
    const layoutConfig = useLayoutConfigStore();
    const VerticalMenu = defineAsyncComponent(() => import("./verticalMenu.vue"));
    //  开始
    const state = reactive({menuList: [] as RouteRecordRaw[],
    });
    onMounted(() => {setFilterRoutes();
    });
    // 过滤菜单(路由)数据
    function setFilterRoutes() {const _filterRoutes = (routes: RouteRecordRaw[]) => {// 只要·非隐藏·菜单const targetRoutes = routes.filter((item) => !item.meta?.hidden);// 递归子路由targetRoutes.forEach((item) => {if (item.children && item.children.length)item.children = _filterRoutes(item.children);});return targetRoutes;};// 拷贝一份,不修改pinia状态值const copyMenuList = JSON.parse(JSON.stringify(authStore.menuList));state.menuList = _filterRoutes(copyMenuList);
    }
    //  结束
    </script><style>
    </style>
  2. 创建一个 src/layout/layoutAside/subMenu.vue 子菜单组件,用于循环复用
<script setup lang='ts' name="SubMenu">
import type { RouteRecordRaw } from "vue-router";
// 父组件传递的prop
defineProps<{menu: RouteRecordRaw;
}>();
</script>
<template><!-- 只有一级菜单,没有子菜单 --><el-menu-itemv-if="!menu.children || menu.children.length <= 0":index="menu.path"><SvgIcon :name="menu.meta?.icon"></SvgIcon><template #title>{{ menu.meta?.title }}</template></el-menu-item><!-- 有子菜单 --><template v-else><el-sub-menu :key="menu.path" :index="menu.path"><template #title><SvgIcon :name="menu.meta?.icon"></SvgIcon><span>{{ menu.meta?.title }}</span></template><!-- 引用当前组件,渲染子组件,注意要组件名 name 值为 SubMenu --><SubMenuv-for="childMenu in menu.children":menu="childMenu":key="childMenu.path"/></el-sub-menu></template>
</template>
  1. 动态渲染左侧主菜单
    src/layout/layoutAside/verticalMenu.vue 子组件中接收传递的 menuList 来渲染数据
<!-- 侧栏(垂直)菜单-->
<script setup lang='ts'>
import { storeToRefs } from "pinia";
import { useLayoutConfigStore } from "../../stores/layoutConfig";
import { defineAsyncComponent } from "vue";
import type { RouteRecordRaw } from "vue-router";
const SubMenu = defineAsyncComponent(() => import("../../layout/layoutAside/subMenu.vue")
);
const layoutConfig = useLayoutConfigStore();
// 是否折叠收起菜单
const { isCollapse } = storeToRefs(layoutConfig);
// 父组件传递的prop
withDefaults(defineProps<{menuList: RouteRecordRaw[];}>(),{menuList: () => [],}
);
</script>
<template><!--滚动条--><el-scrollbar><!--router 开启路由功能,使用 index 属性指定路由地址default-active 默认选中哪个菜单background-color 背景色:default-openeds 默认选中哪个菜单,值为index值数组,要v-bind绑定--><el-menurouter:default-active="$route.path"background-color="transparent":default-openeds="['/system']":collapse="isCollapse"><SubMenu v-for="menu in menuList" :key="menu.path" :menu="menu" /></el-menu></el-scrollbar>
</template>
<style scoped>
</style>

第七步、查看效果:

在这里插入图片描述

按钮级权限控制:

用户登录后,会获取用户拥有的所有按钮权限标识 buttonList ,我们通常可以自定义指令和自定义函数两种方式来进行控制按钮的权限,如果用户没有对应按钮权限,则隐藏该按钮,有权限则显示。
自定义指令:用于组件模板中,显示隐藏对应元素。

  • v-permission="xxx" 单个权限校验 。
  • v-permission="[xxx, xxx]"多个权限校验,满足其中一个则显示 。
  • v-permission-all="[xxx, xxx]" 多个权限验证,全部满足则显示。

自定义函数:用于js逻辑代码中,有权限则执行有权限逻辑代码,无权限则执行无权限逻辑代码。

  • permission('xxx') 单个权限验证。
  • permission(['xxx', 'xxx']) 多个权限验证,满足一个则返回 true
  • permissionAll(['xxx', 'xxx']) 多个权限验证,全部满足则返回 true

第一步:自定义权限指令(全局指令)

自定义权限指令 v-permissionv-permissionsv-permission-all ,创建 src/directive/authDirective.ts 实现:

import type { App } from 'vue';
import { isContainArr } from '@/utils/validate';
import { useAuthStore } from "@/stores/auth";
/**
* 用户权限指令
* 1、v-permission="xxx" 单个权限校验
* 2、v-permissions="[xxx, xxx]" 多个权限校验,满足其中一个则显示
* 3、v-permission-all="[xxx, xxx]" 多个权限验证,全部满足则显示
* 注意:vue3 有了片段支持以后,组件可能会有多个根节点。当被应用在一个多根节点的组件上时,指令会被忽略,并且会抛出一个警告。
* [Vue warn]: Runtime directive used on component with non-element root node. Thedirectives will not function as intended.
*/
export async function permissionDirective(app: App) {const permissionStore = useAuthStore();// v-permission="xxx" 单个权限校验// 第1个参数是指令名,v-permission 使用,而在声明指令名时候不能加 v-app.directive('permission', {mounted(el, binding) {// 获取用户当前所拥有的所有按钮权限const buttonList = permissionStore.buttonList;// 如果没有权限, 将元素移除if (!buttonList.some((btn: string) => btn === binding.value)) {el.parentNode.removeChild(el);}},});// v-permissions="[xxx, xxx]" 多个权限校验,满足其中一个则显示app.directive('permissions', {mounted(el, binding) {// 获取用户当前所拥有的所有按钮权限const buttonList = permissionStore.buttonList;// 存在传递过来的其中一个则返回true,说明有权限if (!buttonList.some((btn: string) => binding.value.indexOf(btn) != -1)) {el.parentNode.removeChild(el);}},});// v-permission-all="[xxx, xxx]" 多个权限验证,全部满足则显示app.directive('permission-all', {mounted(el, binding) {// 获取用户当前所拥有的所有按钮权限const buttonList = permissionStore.buttonList;if (!isContainArr(buttonList, binding.value)) el.parentNode.removeChild(el);},});
}

创建 src/directive/index.ts 作为管理自定义指令的入口,将上面自定义指令导入,方便管理

import type { App } from 'vue';
// 引入所有要注册的全局指令:权限指令
import { permissionDirective } from '@/directive/authDirective';
/**
* 导出指令方法:v-xxx
* @methods permissionDirective 用户权限指令,用法:v-permission
*/
export function directive(app: App) {// 用户权限指令permissionDirective(app);// 其他自定义指令
}

main.ts 中注册自定义全局指令

import { createApp } from 'vue'
import { createPinia } from 'pinia'import App from './App.vue'
import router from './router'// 整合ElementPlus
import ElementPlus from 'element-plus';
// @ts-ignore 汉化
import zhCn from 'element-plus/dist/locale/zh-cn.mjs';
import "@/styles/index.scss"
// 图标
import { useElIcon } from '@/utils/setGlobal';
import '@/router/permission';
// 自定义全局指令
import { directive } from '@/directive';
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus, { locale: zhCn });
// 全局注册图标`ele-`开头(样式在index.scss中)
useElIcon(app);
//注册自定义全局指令 
directive(app);app.mount('#app')

第二步、使用自定义指令控制权限

       <el-buttonv-permission="'system:menu:edit'"linktype="warning"icon="ele-Edit">修改</el-button><el-buttonv-permission="'system:menu:add'"linktype="warning"icon="ele-Edit">新增</el-button><el-buttonv-permission="'system:menu:aaa'"linktype="warning"icon="ele-Edit">新增</el-button>

效果:
Vue路由权限和动态路由

自定义权限函数

有时候,管理后台开发中可能需要在js逻辑代码中进行权限控制,而上面的指令是无法在js代码中使用。我们可以自定义权限函数,实现在js中进行权限控制。自定义函数:用于js逻辑代码中,有权限则执行有权限逻辑代码,无权限则执行无权限逻辑代码。

  • auth('xxx') 单个权限验证 。
  • auths(['xxx', 'xxx']) 多个权限验证,满足一个则返回 true
  • authAll(['xxx', 'xxx']) 多个权限验证,全部满足则返回 true

第一步:定义authFunction函数权限文件

  1. 创建 src/utils/authFunction.ts 自定义权限函数
    import { isContainArr } from '@/utils/validate';
    import { useAuthStore } from "@/stores/auth";
    /**
    * auth('xxx') 单个权限验证
    * 在 js 中使用:
    * import {permission, permissions, permissionAll} from '@/directive/authFunction';
    * const info = auth('goods:add') ? '有权限': '无权限';
    * @param value 权限值
    * @returns 有权限,返回 `true`,反之则反
    */
    export function permission(value: string): boolean {const authStore = useAuthStore();return authStore.buttonList.some((btn: string) => btn === value);
    }
    /**
    * auths(['xxx', 'xxx']) 多个权限验证,满足一个则返回 true
    * @param value 权限值
    * @returns 有权限,返回 `true`,反之则反
    */
    export function permissions(value: string[]): boolean {const authStore = useAuthStore();return authStore.buttonList.some((btn: string) => value.indexOf(btn) != -1)
    }
    /**
    * authAll(['xxx', 'xxx']) 多个权限验证,全部满足则返回 true
    * @param value 权限值
    * @returns 有权限,返回 `true`,反之则反
    */
    export function permissionAll(value: string[]): boolean {const authStore = useAuthStore();return isContainArr(authStore.buttonList, value);
    }
    

第二步、使用自定义的权限函数

<template><div><el-button icon="ele-Plus" type="success" @click="handleAddBefore()">新增用户</el-button></div>
</template><script lang="ts" setup name="statisticBlock">
import { getStatisticsData } from "../../../api/home/index";
import { onMounted, reactive } from "vue";
import { permission } from "../../../utils/authFunction";
import { ElMessage } from "element-plus";
const state = reactive({loading: false,statisticData: {totalOrderNum: 0,totalSaleMoney: 0,totalReturnedMoney: 0,totalIncomeMoney: 0,} as any,
});
onMounted(() => {loadStatisticsData();
});
function handleAddBefore() {if (!permission("system:user:add")) {return ElMessage({message: "没有权限.",type: "warning",});} else {return ElMessage({message: "有权限.",type: "success",});}
}
async function loadStatisticsData() {try {state.loading = true;const { data } = await getStatisticsData();console.log("data", data);// state.statisticData = data;} catch (error) {} finally {state.loading = false;}
}
</script>

第三步、测试使用效果

Vue3路由权限和动态路由

http://www.dtcms.com/wzjs/109073.html

相关文章:

  • 个人网站的设计与建设论文游戏推广文案
  • 在线教育网站做老师靠谱不培训学校加盟
  • 自己做交友网站app拉新怎么做
  • 做网站加模块苏州seo推广
  • 如何在电脑上做网站有友情链接的网站
  • 网站改版意见方案引擎seo优
  • 毕业设计的网站seo排名软件
  • 咨询律师免费解答seo项目
  • 网站建设咨询有客价优网站建设咨seo的基本步骤是什么
  • 网站设计与网页制作毕业论文百度云搜索引擎入口盘搜搜
  • 代办公司注册条件广东宣布即时优化调整
  • 网站描述怎么修改百度竞价推广计划
  • 网站排名优化查询cps推广
  • 在线平面图设计seo网站优化案例
  • 做网站做什么公司好交换友情链接的平台有哪些
  • 违法的网址能注册做一网站用吗搜索引擎关键词优化技巧
  • 支付网站费怎么做会计分录友情链接交换系统
  • 武汉骑士网络做网站中央新闻频道直播今天
  • 谁做的怀来吧网站看广告收益的正规平台
  • 网站建设技术方面的论文广告推广 精准引流
  • 什么样的企业要做网站企业网络推广计划
  • 商丘市网站建设公司职业培训机构有哪些
  • 安装了lnmp怎么做网站上海网站营销推广
  • 网站做非经营性广告需备案安徽企业网站建设
  • 如何做彗聪网站呢seo搜索引擎优化实训报告
  • 做简历的什么客网站公司网址
  • 龙城建设网站公司网络推广视频
  • 微信微网站制作教程百度关键词优化推广
  • 网站制作联盟应用市场
  • 海淀区住房和城乡建设委员会官方网站网络广告营销成功案例