文章目录
前言
最近在做一个通用后台管理系统的框架,通过用户登录拿取用户的权限和菜单列表数据来动态添加路由,使不同用户的显示不同左侧菜单列表。这篇文章主要是讲述通过vue3+router+pinia技术栈设置动态路由菜单。
🚀效果
💥目录
一、用户权限和菜单列表数据
用户登录后拿取token,请求api/sysMenu/nav接口获取的数据。如果没有后端也可以使用mock去模拟数据,这里主要讲实现思路。
{"code":200,"msg":"操作成功","data":{"nav":[{"id":1,"title":"系统管理","icon":"el-icon-s-operation","path":"","name":"sys:manage","component":"","children":[{"id":2,"title":"用户管理","icon":"el-icon-s-custom","path":"/sys/users","name":"sys:user:list","component":"sys/User","children":[{"id":9,"title":"添加用户","icon":null,"path":null,"name":"sys:user:save","component":null,"children":[]},{"id":10,"title":"修改用户","icon":null,"path":null,"name":"sys:user:update","component":null,"children":[]},{"id":11,"title":"删除用户","icon":null,"path":null,"name":"sys:user:delete","component":null,"children":[]},{"id":12,"title":"分配角色","icon":null,"path":null,"name":"sys:user:role","component":null,"children":[]},{"id":13,"title":"重置密码","icon":null,"path":null,"name":"sys:user:repass","component":null,"children":[]}]},{"id":3,"title":"角色管理","icon":"el-icon-rank","path":"/sys/roles","name":"sys:role:list","component":"sys/Role","children":[{"id":7,"title":"添加角色","icon":"","path":"","name":"sys:role:save","component":"","children":[]},{"id":14,"title":"修改角色","icon":null,"path":null,"name":"sys:role:update","component":null,"children":[]},{"id":15,"title":"删除角色","icon":null,"path":null,"name":"sys:role:delete","component":null,"children":[]},{"id":16,"title":"分配权限","icon":null,"path":null,"name":"sys:role:perm","component":null,"children":[]}]},{"id":4,"title":"菜单管理","icon":"el-icon-menu","path":"/sys/menus","name":"sys:menu:list","component":"sys/Menu","children":[{"id":17,"title":"添加菜单","icon":null,"path":null,"name":"sys:menu:save","component":null,"children":[]},{"id":18,"title":"修改菜单","icon":null,"path":null,"name":"sys:menu:update","component":null,"children":[]},{"id":19,"title":"删除菜单","icon":null,"path":null,"name":"sys:menu:delete","component":null,"children":[]}]}]},{"id":5,"title":"系统工具","icon":"el-icon-s-tools","path":"","name":"sys:tools","component":null,"children":[{"id":6,"title":"数字字典","icon":"el-icon-s-order","path":"/sys/dicts","name":"sys:dict:list","component":"sys/Dict","children":[]}]},{"id":20,"title":"管理员","icon":null,"path":null,"name":"sys:user","component":"sys/Normal","children":[]}],"authoritys":["ROLE_admin","ROLE_normal","sys:manage","sys:user:list","sys:role:list","sys:menu:list","sys:tools","sys:dict:list","sys:role:save","sys:user:save","sys:user:update","sys:user:delete","sys:user:role","sys:user:repass","sys:role:update","sys:role:delete","sys:role:perm","sys:menu:save","sys:menu:update","sys:menu:delete","sys:user"]}}
二、pinia存储数据状态共享
tips: pinia的引入去官网看看就能上手
1.创建存储用户详情的user.ts文件
// @src/store/user.tsimport{ defineStore }from'pinia'import{ logout }from'@/api/user'import{ ref }from'vue';exportconst useUserStore =defineStore('user',()=>{const token =ref("")const userInfo =ref({}as UserInfo)functionSET_TOKEN(name: string){
token.value = name
localStorage.setItem("token", name)}functionSET_INFO(user: UserInfo){
userInfo.value = user
}asyncfunctionremove(){awaitlogout()
localStorage.clear()
sessionStorage.clear()SET_INFO({}as UserInfo)}return{persist:true,
token,
userInfo,
remove,SET_TOKEN,SET_INFO}})
2.创建存储用户菜单和权限的menus.ts文件
// @src/store/menus.tsimport{ defineStore }from'pinia'import{ ref }from'vue';exportconst useMeanStore =defineStore('mean',()=>{// 菜单数据const menuList =ref([])// 权限数据const permList =ref([])const hasRoute =ref(false)functionchangeRouteStatus(state: any){
hasRoute.value = state
sessionStorage.setItem("hasRoute", state)}functionsetMenuList(menus: any){
menuList.value = menus
}functionsetPermList(authoritys: any){
permList.value = authoritys
}return{
menuList,
permList,
hasRoute,
changeRouteStatus,
setMenuList,
setPermList
}})
三、设置动态路由
1.在router文件夹下面创建routers.ts文件
提示:main.ts文件下导入 import router from '@/router/routers' 然后挂载实例app.use(router)
import{ createRouter, createWebHistory, RouteRecordRaw }from'vue-router';import Index from'../views/Index.vue'import Layout from'../layout/index.vue'import{ nav }from'@/api/system/menu'import{ useMeanStore }from'@/store/menus'constroutes: Array<RouteRecordRaw>=[{path:'/login',name:'Login',meta:{title:'登录',keepAlive:true,requireAuth:false},component:()=>import('@/views/login.vue')},{path:'/redirect',name:'Redirect',component: Layout,children:[{path:'/index',name:'Index',meta:{title:"首页"},component: Index
},]},{path:'/',component: Layout,redirect:'/dashboard',children:[{path:'dashboard',component: Index,name:'Dashboard',meta:{title:'首页',icon:'index',affix:true,noCache:true}}]}]const router =createRouter({history:createWebHistory(),
routes
})exportdefault router
2.设置前置路由守卫
- beforeEach有三个参数
to: 即将要进入的目标
、from: 当前导航正要离开的路由
、可选的第三个参数 next:进入下一个目标
,beforeEach个人理解是一个路由过滤器,在路由前进行判断的处理。
// @router/routers.ts 中添加前置路由守卫
router.beforeEach((to, from, next)=>{let token = localStorage.getItem("token")// 注意:在beforeEach中调用pinia存储的菜单状态是为了避免` Did you forget to install pinia?`这个bugconst useMean =useMeanStore()
console.log('hasRoute', useMean.hasRoute)if(to.path =='/login'){
console.log("login!!!!!!!!!!!")next()}elseif(!token){
console.log("还没有token!!!")next({path:"/login"})}elseif(to.path =='/'|| to.path ==''){next({path:'/'})}elseif(!useMean.hasRoute){nav().then(res=>{
useMean.setMenuList(res.data.nav)
useMean.setPermList(res.data.authoritys)
res.data.nav.forEach((menu:{children: any[];})=>{if(menu.children){
menu.children.forEach((e: any)=>{if(!e.component){return}letroute:any ={name: e.name,path: e.path,meta:{icon: e.icon,title: e.title
},// 用这行部署服务器上跳转会有问题// component: () =>import('../views/' + e.component +'.vue')// 解决跳转问题component: modules['../views/'+ e.component +'.vue']}
router.addRoute("Redirect", route)})}})})
useMean.changeRouteStatus(true)next({path: to.path})}else{
console.log("已经有路由了------------")next()}})
✨完整@router/routers.ts代码
import{ createRouter, createWebHistory, RouteRecordRaw }from'vue-router';import Index from'../views/Index.vue'import Layout from'../layout/index.vue'import{ nav }from'@/api/system/menu'import{ useMeanStore }from'@/store/menus'constroutes: Array<RouteRecordRaw>=[{path:'/login',name:'Login',meta:{title:'登录',keepAlive:true,requireAuth:false},component:()=>import('@/views/login.vue')},{path:'/redirect',name:'Redirect',component: Layout,children:[{path:'/index',name:'Index',meta:{title:"首页"},component: Index
},]},{path:'/',component: Layout,redirect:'/dashboard',children:[{path:'dashboard',component: Index,name:'Dashboard',meta:{title:'首页',icon:'index',affix:true,noCache:true}}]}]const router =createRouter({history:createWebHistory(),
routes
})
router.beforeEach((to, from, next)=>{let token = localStorage.getItem("token")const useMean =useMeanStore()
console.log('hasRoute', useMean.hasRoute)if(to.path =='/login'){
console.log("login!!!!!!!!!!!")next()}elseif(!token){
console.log("还没有token!!!")next({path:"/login"})}elseif(to.path =='/'|| to.path ==''){next({path:'/'})}elseif(!useMean.hasRoute){nav().then(res=>{
useMean.setMenuList(res.data.nav)
useMean.setPermList(res.data.authoritys)
res.data.nav.forEach((menu:{children: any[];})=>{if(menu.children){
menu.children.forEach((e: any)=>{if(!e.component){return}letroute:any ={name: e.name,path: e.path,meta:{icon: e.icon,title: e.title
},component:()=>import('../views/'+ e.component +'.vue')}
router.addRoute("Redirect", route)})}})})
useMean.changeRouteStatus(true)next({path: to.path})}else{
console.log("已经有路由了------------")next()}})exportdefault router
描述:通过前置路由守卫拦截路由处理,在登录页判断是否有token,如果没有token继续去登陆,登录成功后拿取token使用pinia存储状态,再判断是否有路由hasRoute没有就请求api/sysMenu/nav接口获取的数据,添加路由并存储菜单数据和权限数据到pinia状态中共享数据。
3.左侧导航菜单
beforeEach中已经把用户菜单和权限存储到@src/store/menus.ts中,在Sidebar.vue中引用import { useMeanStore } from ‘@/store/menus’ 拿取菜单数据渲染菜单列表
<template><div><el-menu
active-text-color="#ffd04b"
background-color="#304156"class="el-menu-vertical-demo"default-active="2"
text-color="#fff"
@open="handleOpen"
@close="handleClose"><el-sub-menu default-active="Index":index="menu.name" v-for="menu in menuList":key="menu.name"><template #title><el-icon><location /></el-icon><span>{{menu.title}}</span></template><router-link :to="item.path" v-for="item in menu.children":key="item.name"><el-menu-item :index="item.name"><template #title><i :class="item.icon"></i><span slot="title">{{item.title}}</span></template></el-menu-item></router-link></el-sub-menu></el-menu></div></template><script setup lang="ts">import{ reactive }from'vue';import{ useMeanStore }from'@/store/menus';import{ storeToRefs }from"pinia";const useMean =useMeanStore()const{ menuList, permList, hasRoute }=storeToRefs(useMean);constsetMenuList:any = localStorage.getItem("setMenuList")
console.log('setMenuList', menuList)consthandleOpen=(key: string,keyPath: string[])=>{
console.log(key, keyPath);};consthandleClose=(key: string,keyPath: string[])=>{
console.log(key, keyPath);};</script><style lang="scss" scoped></style>
动态路由设置完成效果就会如上效果图
版权归原作者 山海入梦 所有, 如有侵权,请联系我们删除。