先定义项目静态路由
route/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
// 使用俩个独立的数组分别管理动态路由和静态路由
// 所有的动态路由 不同用户看到的数量不一致
// 静态路由 每个用户都可以看到
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/Login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
},
{
path: '/import',
component: Layout,
hidden: true,
children: [{
path: '',
name: 'import',
component: () => import('@/views/ImportExcel/index.vue'),
meta: { title: '导入' }
}]
},
{
path: '/detail',
component: Layout,
hidden: true,
children: [{
path: '',
name: 'detail',
component: () => import('@/views/Employee/detail.vue'),
meta: { title: '员工详情' }
}]
}
]
const createRouter = () => new Router({
// mode: 'history', // require service support
mode: 'history', // 路由模式 默认是hash
scrollBehavior: () => ({ y: 0 }), // 路由的滚动行为
routes: [...constantRoutes]
})
const router = createRouter() // 每次调用都会拿到一个新的router实例对象
// 这个方法就是用来重置路由的方法 只要调用此方法就可以把先前的路由记录清空
export function resetRouter() {
// 得到一个全新的router实例对象
const newRouter = createRouter()
// 使用新的路由记录覆盖掉老的路由记录
// 包含了所有的路由记录 path - component
router.matcher = newRouter.matcher // reset router
}
export default router
在定义动态路由
router/asyncRouters.js
import Layout from '../layout'
// 所有的动态路由表
// Layout组件作用: 提供一个外层的框架 左侧 右边 顶部
// 二级路由path置空: 如果当前的二级路由path为空 那么它将作为默认的二级路由渲染
// 访问的是一级路由的路径 但是二级也会跟着一起渲染
export const asyncRoutes = [
// 组织架构
{
path: '/department',
component: Layout,
children: [{
path: '',
name: 'departments',
component: () => import('@/views/Department/index'),
meta: { title: '组织架构', icon: 'tree' }
}]
},
// 角色管理
{
path: '/setting',
component: Layout,
children: [{
path: '',
name: 'settings',
component: () => import('@/views/Setting/index'),
meta: { title: '角色管理', icon: 'setting' }
}]
},
// 员工管理
{
path: '/employee',
component: Layout,
children: [{
path: '',
name: 'employees',
component: () => import('@/views/Employee/index'),
meta: { title: '员工管理', icon: 'people' }
}]
},
// 权限点
{
path: '/permission',
component: Layout,
children: [{
path: '',
name: 'permissions',
component: () => import('@/views/Permission/index'),
meta: { title: '权限点管理', icon: 'lock' }
}]
},
{
path: '/salarys',
component: Layout,
children: [{
path: '', // 如果children path置空的话 当前这个路由会作为一级渲染的默认路由
name: 'salarys',
component: () => import('@/views/Salary'),
meta: { title: '工资管理', icon: 'money' }
}]
}
]
路由前置守卫
// 权限控制
// 1. 路由跳转权限控制
// 2. 菜单权限控制
import router from '@/router'
import store from '@/store'
import { getToken } from './utils/auth'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
// 白名单
const WHITELIST = ['/login', '/404']
// 导入动态路由表
import { asyncRoutes } from '@/router/asyncRouters'
router.beforeEach(async(to, from, next) => {
// to: 到哪里去 目标路由对象
// from: 从哪里来 来源路由对象
// next: 放行函数[在分支中都应该有且只有一个next]
NProgress.start() // 开启进度条
const token = getToken()
if (token) {
// 有token
if (to.path === '/login') {
next('/')
} else {
next()
// 最合适的位置 满足以上三个条件 调用action函数
// 如果当前已经调用过了 vuex已经存在数据 就不需要重复调用
if (!store.state.user.userInfo.userId) {
// 获取用户信息的接口
const res = await store.dispatch('user/fetchGetUserInfo')
// 权限控制在这里进行实现
// 1. 拿到俩份数据 一份menus标识数据 一份动态路由表数据
const menus = res.roles.menus
// 2. 以menus为主对asyncRoutes做过滤 条件是: 需要判断动态路由表中的每一项
// route.chilren[0].name 尝试去menus中找 如果找到了代表有资格 如果找不到就代表没有资格
const filterRoutes = asyncRoutes.filter(route => menus.includes(route.children[0].name))
// 3. 把过滤之后的路由加入到路由系统中
// 路由系统: 浏览器中访问一个路径path 可以渲染对应的组件
// 本地的动态路由默认情况下 不加入到路由系统中 而是把这些符合条件的加入到路由系统中
// vue-router提供了一个方法专门做这件事儿 addRoutes
// 如果你配置了兜底路由 path * 必须把它放到整个路由表的末尾 [静态 + 动态 + *]
// 有问题的是这样的 [静态的 + * + 动态]
router.addRoutes([...filterRoutes, { path: '*', redirect: '/404', hidden: true }])
// 4. 把filterRoutes显示到左侧的菜单中
// 实现思路: 之前渲染左侧菜单时使用的数据是静态的 是永远不变的 哪怕我们把新的动态路由
// 加入到了路由系统中 它也不会自动变化
// 让渲染左侧菜单的路由变成可变的? -> 什么东西是响应式的呢? data computed vuex
// 结论:我们需要单独通过vuex维护我们左侧菜单的数据
store.commit('menu/setMenuList', filterRoutes)
}
}
} else {
// 没有token 数组中是否找得到某项 数组方法?includes
if (WHITELIST.includes(to.path)) {
// 在白名单内
next()
} else {
next('/login')
}
}
NProgress.done() // 结束进度条
})
vuex
import { constantRoutes } from '@/router'
export default {
// 开启模块命名空间 才算是严格的模块化管理
// 访问 mutation action函数的时候 都需要在前面加上模块名称才可以
namespaced: true,
// constantRoutes是静态路由
state: () => ({
menuList: [...constantRoutes] // 路由表(菜单表)
}),
mutations: {
// 只需要调用这个方法 传入过滤之后的动态路由表 完成静态+动态的组合
setMenuList(state, filterAsyncRoutes) {
state.menuList = [...constantRoutes, ...filterAsyncRoutes]
}
}
}
html
<!-- 左侧菜单组件 -->
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<!-- 菜单中的每一项 -->
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
computed: {
routes() {
// 路由使用的数组
// router.options对象内置的属性 固定的
// 里面拿到的是我们在router.js中实例化路由对象的时候传入给routes选项的数组
// 这里拿到的就是初始化路由的所有路由表 [静态也包含动态]
// 特点就是静态的: 一旦项目启动起来就不会再发生变化
return this.$store.state.menu.menuList
},
}
退出登录重置路由
现存问题
假如A用户先进行的登录 它的菜单权限是 ['工资','社保'],退出登录之后使用B用户登录,此时B用户的菜单权限为['工资'],
但是因为上一个路由记录还在,所以B用户同样可以访问到社保 模块,这显然是不对的,
所以要求我们在退出登录的时候先清除一下先前的路由记录,本质上是因为addRoutes方法是一个累加的方法
解决方案
vue-router 提供了addRoute来进行添加路由,但是却没有移除路由的相关选项,
当切换用户的时候,想要移除路由再添加怎么办呢?可以另外建一个router实例来替换之前的实例。
我们的router/index.js文件,发现一个重置路由方法
// 重置路由
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // 重新设置路由的可匹配路径
}
这个方法就是将路由重新实例化,相当于换了一个新的路由,之前加的路由就不存在了,需要在登出的时候, 调用一下即可
import { resetRouter } from '@/router'
// 退出的action操作
logout(context) {
// 1. 移除vuex个人信息
context.commit('removeUserInfo')
// 2. 移除token信息
context.commit('removeToken')
// 3. 重置路由系统
resetRouter()
}