用户角色不同 看到菜单不同 目前用户密码默认都是6个1
4-1 修改src/permission.ts
路由导航里权限验证
先判断是否 有token 如果有 判断目前有没有角色,没有就根据token去获取用户信息包含角色信息 然后根据用户角色获取权限菜单 然后在根据权限菜单 筛选asyncRoutes 注册相应的权限路由 并渲染返回的菜单
src/permission.ts
import router from '@/router'
import nProgress from 'nprogress'
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from './utils/auth'
import store from '@/store'
import { ElMessage } from 'element-plus'
nProgress.configure({ showSpinner: false })
const whiteList = ['/login'] // 白名单
router.beforeEach(async (to) => {
nProgress.start()
const hasToken = getToken()
if (hasToken) { // 有token代表已登录
if (to.path === '/login') {
nProgress.done()
return {
path: '/',
replace: true
}
} else {
try {
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
nProgress.done()
return true
}
// 无用户信息和角色信息 就请求获取
const roles = await store.dispatch('user/getUserInfo')
// 该用户未分配角色 进行异常提示
if (!roles || roles.length === 0) {
throw new Error('该用户未分配角色')
}
// 获取权限路由
const accessRoutes = await store.dispatch('permission/generateRoutes')
// 动态注册路由
accessRoutes.forEach(router.addRoute)
// 触发重定向
return to.fullPath
} catch (error) { // 登录失败处理
// 移除token重新登录
await store.dispatch('user/resetToken')
ElMessage.error('登录失败:' + (error.message || 'Has Error'))
nProgress.done()
return `/login?redirect=${to.path}`
}
}
} else {
if (whiteList.includes(to.path)) {
nProgress.done()
return true
}
nProgress.done()
return {
path: '/login',
query: {
redirect: to.path,
...to.query
}
}
}
})
router.afterEach(() => {
nProgress.done()
})
4-2 permissions store
src/store/modules/permission.ts
import { Module, MutationTree, ActionTree } from 'vuex'
import { RouteRecordRaw } from 'vue-router'
import store, { IRootState } from '../index'
import { asyncRoutes } from '../../router/index'
import { MenuData } from './menu'
import path from 'path'
// 生成路由路径数组
const generateRoutePaths = (menus: Array<MenuData>): string[] => {
return menus.map(menu => menu.path)
}
// 白名单
const whiteList = ['/:pathMatch(.*)*']
// 生成可访问路由表
const generateRoutes = (routes: Array<RouteRecordRaw>, routePaths: string[], basePath = '/') => {
const routerData: Array<RouteRecordRaw> = []
routes.forEach(route => {
const routePath = path.resolve(basePath, route.path)
if (route.children) { // 先看子路由 是否有匹配上的路由
route.children = generateRoutes(route.children, routePaths, routePath)
}
// 如果当前路由子路由 数量大于0有匹配上 或 paths中包含当面路由path 就需要把当前父路由添加上
if (routePaths.includes(routePath) || (route.children && route.children.length >= 1) || whiteList.includes(routePath)) {
routerData.push(route)
}
})
return routerData
}
const filterAsyncRoutes = (menus: Array<MenuData>, routes: Array<RouteRecordRaw>) => {
// 生成要匹配的路由path数组
const routePaths = generateRoutePaths(menus)
// 生成匹配path的路由表
const routerList = generateRoutes(routes, routePaths)
return routerList
}
// 定义state类型
export interface IPermissionState {
routes: Array<RouteRecordRaw>;
accessRoutes: Array<RouteRecordRaw>;
}
// mutations类型
type IMutations = MutationTree<IPermissionState>
// actions类型
type IActions = ActionTree<IPermissionState, IRootState>
// 定义state
const state: IPermissionState = {
routes: [],
accessRoutes: []
}
// 定义mutation
const mutations: IMutations = {
SET_ROUTES(state, data: Array<RouteRecordRaw>) {
state.routes = data
},
SET_ACCESS_ROUTES(state, data: Array<RouteRecordRaw>) {
state.accessRoutes = data
}
}
// 定义actions
const actions: IActions = {
generateRoutes({ dispatch }, type?: number) { // 1 针对菜单排序更新
return new Promise((resolve, reject) => {
let accessedRoutes: Array<RouteRecordRaw> = []
if (store.getters.roleNames.includes('super_admin')) { // 超级管理员角色
accessedRoutes = asyncRoutes
dispatch('menu/getAllMenuListByAdmin', null, { root: true })
resolve(accessedRoutes)
} else { // 根据角色过滤菜单
const roles = store.getters.roleIds
dispatch('menu/getAccessByRoles', roles, { root: true }).then(menus => {
if (type !== 1) { // 菜单重新排序 不需要再过次滤路由
accessedRoutes = filterAsyncRoutes(menus, asyncRoutes)
}
resolve(accessedRoutes)
}).catch(reject)
}
})
}
}
// 定义user module
const permission: Module<IPermissionState, IRootState> = {
namespaced: true,
state,
mutations,
actions
}
export default permission
4-5 修改路由表
路由表之前 数组 合并导出的路由 现在不需要了
/src/router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'
// 看作是异步获取路由
export const asyncRoutes: Array<RouteRecordRaw> = [
{
path: '/documentation',
component: Layout, // 布局组件作为一级路由
redirect: '/documentation/index',
name: 'DocumentationLayout',
children: [
{
path: 'index',
name: 'Documentation',
component: () => import(/* webpackChunkName: "documentation" */ '@/views/documentation/index.vue'),
meta: {
title: 'Documentation',
icon: 'documentation',
hidden: false, // 菜单栏不显示
// 路由是否缓存 没有这个属性或false都会缓存 true不缓存
noCache: true
}
}
]
},
{
path: '/async',
component: Layout,
redirect: '/async/index',
name: 'AsyncLayout',
children: [
{
path: 'index',
name: 'Async',
component: () => import(/* webpackChunkName: "async" */ '@/views/async.vue'),
meta: {
title: '动态路由',
icon: 'guide'
// 当guide路由激活时高亮选中的是 documentation/index菜单
// activeMenu: '/documentation/index'
}
}
]
},
{
path: '/guide',
component: Layout,
redirect: '/guide/index',
name: 'GuideLayout',
meta: {
title: 'GuideLay',
icon: 'guide'
},
children: [
{
path: 'index',
name: 'Guide',
component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
meta: {
title: 'Guide',
icon: 'guide'
// 当guide路由激活时高亮选中的是 documentation/index菜单
// activeMenu: '/documentation/index'
}
},
{
path: 'guide2',
name: 'Guide2',
component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
meta: {
title: 'Guide2',
icon: 'guide'
// 当guide路由激活时高亮选中的是 documentation/index菜单
// activeMenu: '/documentation/index'
}
},
{
path: 'guide3',
name: 'Guide3',
component: () => import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
meta: {
title: 'Guide3',
icon: 'guide'
// 当guide路由激活时高亮选中的是 documentation/index菜单
// activeMenu: '/documentation/index'
}
}
]
},
{
path: '/system',
component: Layout,
redirect: '/system/user',
name: 'SystemLayout',
meta: {
title: 'System',
icon: 'lock',
alwaysShow: true // 根路由始终显示 哪怕只有一个子路由
},
children: [
{
path: 'menu',
name: 'Menu Management',
component: () => import(/* webpackChunkName: "menu" */ '@/views/system/menu/index.vue'),
meta: {
title: 'Menu Management',
hidden: false,
breadcrumb: false
}
},
{
path: 'role',
name: 'Role Management',
component: () => import(/* webpackChunkName: "role" */ '@/views/system/role/index.vue'),
meta: {
title: 'Role Management',
hidden: false
}
},
{
path: 'user',
name: 'User Management',
component: () => import(/* webpackChunkName: "user" */ '@/views/system/user/index.vue'),
meta: {
title: 'User Management'
}
}
]
},
{ // 外链路由
path: '/external-link',
component: Layout,
children: [
{
path: 'https://www.baidu.com/',
redirect: '/',
meta: {
title: 'External Link',
icon: 'link'
}
}
]
},
{ // 404一定放在要在最后面
path: '/:pathMatch(.*)*',
redirect: '/404',
meta: {
hidden: true
}
}
]
export const constantRoutes: Array<RouteRecordRaw> = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'DashboardLayout',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'),
meta: {
title: 'Dashboard',
// icon: 'dashboard'
icon: 'el-icon-platform-eleme',
affix: true // 固定显示在tagsView中
}
}
]
},
{
path: '/redirect',
component: Layout,
meta: {
hidden: true
},
name: 'Redirect',
children: [
{ // 带参数的动态路由正则匹配
// https://next.router.vuejs.org/zh/guide/essentials/route-matching-syntax.html#%E5%8F%AF%E9%87%8D%E5%A4%8D%E7%9A%84%E5%8F%82%E6%95%B0
path: '/redirect/:path(.*)', // 要匹配多级路由 应该加*号
component: () => import('@/views/redirect/index.vue')
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue')
},
{
path: '/profile',
component: Layout,
redirect: '/profile/index',
name: 'ProfileLayout',
children: [
{
path: 'index',
name: 'Profile',
component: () => import('@/views/profile/index.vue'),
meta: {
hidden: true,
title: '个人中心'
}
}
]
},
{
path: '/401',
component: Layout,
name: '401Layout',
children: [
{
path: '',
component: () => import('@/views/error-page/401.vue'),
meta: {
title: '401',
icon: '404',
hidden: true
}
}
]
},
{
path: '/404',
component: () => import('@/views/error-page/404.vue'),
meta: {
hidden: true // 404 hidden掉
}
}
]
// export const routes = [
// ...constantRoutes,
// ...asyncRoutes
// ]
export const routes = constantRoutes
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
4-4 修改侧边栏
从权限store里获取菜单
src/layout/components/Sidebar/index.vue
<template>
<div class="sidebar-wrapper">
<logo v-if="showLogo" :collapse="isCollapse" />
<scroll-panel>
<el-menu
class="sidebar-container-menu"
:class="{
'sidebar-show-logo': showLogo
}"
mode="vertical"
:default-active="activeMenu"
:background-color="scssVariables.menuBg"
:text-color="scssVariables.menuText"
:active-text-color="themeColor"
:collapse="isCollapse"
:collapse-transition="true"
>
<sidebar-item
v-for="route in menuRoutes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</scroll-panel>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useRoute } from 'vue-router'
import variables from '@/styles/variables.scss'
import SidebarItem from './SidebarItem.vue'
import { useStore } from '@/store'
import Logo from './Logo.vue'
import ScrollPanel from '@/components/ScrollPanel.vue'
export default defineComponent({
name: 'Sidebar',
components: {
Logo,
SidebarItem,
ScrollPanel
},
setup() {
const route = useRoute()
const store = useStore()
// 根据路由路径 对应 当前激活的菜单
const activeMenu = computed(() => {
const { path, meta } = route
// 可根据meta.activeMenu指定 当前路由激活时 让哪个菜单高亮选中
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
// scss变量
const scssVariables = computed(() => variables)
// 展开收起状态 稍后放store 当前是展开就让它收起
const isCollapse = computed(() => !store.getters.sidebar.opened)
// 获取权限菜单
const menuList = computed(() => store.state.menu.authMenuTreeData)
// 渲染路由
const menuRoutes = computed(() => [...menuList.value])
// 获取主题色
const themeColor = computed(() => store.getters.themeColor)
// 是否显示logo
const showLogo = computed(() => store.state.settings.sidebarLogo)
return {
// ...toRefs(variables), // 不用toRefs原因 缺点variables里面变量属性来源不明确
scssVariables,
isCollapse,
activeMenu,
menuRoutes,
themeColor,
showLogo
}
}
})
</script>
<style lang="scss" scoped>
.sidebar-wrapper {
.sidebar-container-menu {
height: 100vh;
&.sidebar-show-logo { // 显示logo时
// 100vh-50px
height: calc(100vh - 50px);
}
}
}
</style>