赞
踩
项目背景:登录的时候,后端在返回token的同时还一并返回用户的登录权限,且我司返回的是一串数组,里面的内容对应每个要显示的路由,没有admin之类的权限。
实现流程(具体看代码,超级详细):
实现过程遇到的bug:
修改路由结构:
把需要动态加载的菜单放在asyncRoutes里面,并给每个路由加个角色(父路由也需要的哦),放在meta里哦,【分为constantRoutes(静态路由) 和 asyncRoutes(动态路由)】
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: "/", component: Layout, redirect: "/dashboard", children: [ { path: "dashboard", name: "主页", component: () => import("@/views/dashboard/index"), meta: { title: "主页", icon: "dashboard" } } ] }, { path: "/external-link", component: Layout, children: [ { path: "/", meta: { title: "关于我们", icon: "link" } } ] }, { path: "/404", component: () => import("@/views/404"), hidden: true }, { path: "*", redirect: "/404", hidden: true }, ]; export const asyncRoutes = [ { path: "/example", component: Layout, redirect: "/example/table", name: "Example", meta: { title: "个人管理", icon: "el-icon-user-solid", roles: ['Personal'] }, children: [ { path: "table", name: "Table3", component: () => import("@/views/table/index"), meta: { title: "单", roles: ['Personal'] } }, { path: "tree", name: "Tree3", component: () => import("@/views/table/rate"), // meta: { title: "进度", icon: "tree", roles: ['Personal'] } } ] }, { path: "/proxy", component: Layout, redirect: "proxy/manage", meta: { title: "服务商管理", icon: "el-icon-office-building", roles: ['maintainCompanyPolicy', 'maintainCompanyEmployee'] }, children: [ { path: "manage", name: "Proxy", component: () => import("@/views/manage/index"), meta: { title: "单", roles: ['maintainCompanyPolicy'] } }, { path: "employee", name: "Employee", component: () => import("@/views/manage/employee"), // meta: { title: "管理", roles: ['maintainCompanyEmployee'] } } ] }, { path: "/company", component: Layout, redirect: "company/maintain/table", name: "Admin", meta: { title: "后台管理", icon: "el-icon-s-help", roles: ['maintainAdminCompany'] }, children: [ { path: "maintain", component: () => import("@/views/company/index"), // Parent router-view name: "Maintain", redirect: "maintain/table", meta: { title: "公司", roles: ['maintainAdminCompany'] }, children: [ { path: "table", name: "Table2", component: () => import("@/views/company/table"), meta: { title: "公司", roles: ['maintainAdminCompany'] } } ] }, { path: "manage", component: () => import("@/views/company/maintain/index"), name: "Manage", meta: { title: "维保单", roles: ['maintainAdminCompany'] } }, { path: "recycle", component: () => import("@/views/company/recycle/index"), name: "Unusual", meta: { title: "回收站", roles: ['maintainAdminCompany'] } } ] }, ] const createRouter = () => new Router({ scrollBehavior: () => ({ y: 0 }), routes: constantRoutes }); const router = createRouter(); export function resetRouter () { const newRouter = createRouter(); router.matcher = newRouter.matcher; // reset router } export default router;
把后端传来的角色存储起来:
import { login, logout, getInfo } from "@/api/user"; import { getToken, setToken, removeToken, setUser, removeUser } from "@/utils/auth"; import { resetRouter } from "@/router"; const getDefaultState = () => { return { token: getToken(), //token role: [], //角色列表 init: false //这个是解决后面‘切换用户,左侧路由菜单不变,要刷新一下才可以’的问题,如果在退出时刷新了,就不用写这个 }; }; const state = getDefaultState(); const mutations = { RESET_STATE: state => { Object.assign(state, getDefaultState()); }, SET_TOKEN: (state, token) => { state.token = token; } SET_ROLE: (state, role) => { state.role = role; } // 判断是否初次登陆,在src/permission.js用到init SET_INIT: (state, data) => { state.init = data } }; const actions = { //初次登陆赋予init为true changeInit ({ commit }) { commit("SET_INIT", true); }, // 账号密码登录 login ({ commit }, userInfo) { const menulist = [] const { username, password } = userInfo; const formdata = new FormData(); formdata.append('username', username.trim()) formdata.append('password', password) return new Promise((resolve, reject) => { login(formdata) .then(response => { const data = response.data; // 权限 const role = data.userAuth.userAuth.map(roles => roles.moduleCode) //将角色列表格式化 localStorage.setItem('role', JSON.stringify(role)) // 将角色存储在本地 commit("SET_ROLE", role); setToken(data.token.value); resolve(); }) .catch(error => { reject(error); }); }); } // 退出登录 logout ({ commit, state, dispatch }) { return new Promise((resolve, reject) => { logout(state.token) .then(() => { // location.reload() //退出 刷新页面,如果写了这个就不用写init了,选用init 只是因为个人感觉比较优雅 localStorage.removeItem('role') commit('SET_INIT', false) commit('SET_TOKEN', ''); commit("SET_ROLE", ''); removeToken(); // must remove token first removeUser(); commit("RESET_STATE"); commit('SET_MENULIST', ''); dispatch('tagsView/delAllViews', null, { root: true }); resetRouter(); resolve(); }) .catch(error => { reject(error); }); }); }, // remove token resetToken ({ commit }) { return new Promise(resolve => { removeToken(); // must remove token first removeUser(); commit("RESET_STATE"); resolve(); }); } }; export default { namespaced: true, state, mutations, actions };
这个别漏啦
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
account: state => state.user.account,
name: state => state.user.name,
role: state => state.user.role, //加上这个
permission_routes: state => state.permission.routes, //别漏了这个哦
init: state => state.user.init //加上这个
}
export default getters
动态添加路由:
import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' // 进度条 import 'nprogress/nprogress.css' // progress bar style import { getToken } from '@/utils/auth' // get token from cookie import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) // 是否有转圈效果 const whiteList = ['/login'] // 没有重定向的白名单 router.beforeEach(async (to, from, next) => { // 开始进度条 NProgress.start() // 设置页面标题 document.title = getPageTitle(to.meta.title) // 确定页面是否已登录 const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // 如果已登录,则重定向到主页 next({ path: '/' }) NProgress.done() } else { // 获取到的静态路由 + 动态动态,如果选择的方案是不使用init,则不要注释此条,把下面的a注释掉 // const hasGetPermissionRoutes = store.getters.permission_routes && store.getters.permission_routes.length > 0 // 判断是否第一次登陆 const a = store.getters.init if (a) { next() } else { try { // const roles = store.state.user.role,原文档写法,但是这样的话 会导致刷新后,数据丢失 const roles = JSON.parse(localStorage.getItem('role')) const accessRoutes = await store.dispatch('permission/generateRoutes', roles) await store.dispatch('user/changeInit') router.addRoutes(accessRoutes) // 在这里动态添加最后的通配路由,确保先有动态路由,再有通配路由,解决动态路由刷新会跳转到404问题 let lastRou = [{ path: '*', redirect: '/404' }] router.addRoutes(lastRou) next({ ...to, replace: true }) } catch (error) { // 删除令牌并进入登录页面重新登录 await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // finish progress bar NProgress.done() })
筛选应该被添加的路由:
import { asyncRoutes, constantRoutes } from '@/router' /** * Use meta.role to determine if the current user has permission * @param roles * @param route */ function hasPermission (roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } /** * Filter asynchronous routing tables by recursion * @param routes asyncRoutes * @param roles */ export function filterAsyncRoutes (routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } const state = { routes: [], addRoutes: [] } const mutations = { SET_ROUTES: (state, routes) => { state.addRoutes = routes // 权限路由放在最后 // state.routes = constantRoutes.concat(routes) // 把权限路由放在中间,在constontRoutes let rou = constantRoutes rou.splice(2, 0, ...routes) //把第二个放在最后 state.routes = rou } } const actions = { generateRoutes ({ commit }, roles) { return new Promise(resolve => { const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions }
渲染路由导航:
<template> <div :class="{'has-logo':showLogo}"> <logo v-if="showLogo" :collapse="isCollapse" /> <el-scrollbar wrap-class="scrollbar-wrapper"> <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" > //这里一定要注意修改为 permission_routes <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" /> </el-menu> </el-scrollbar> </div> </template> <script> import { mapGetters } from 'vuex' import Logo from './Logo' import SidebarItem from './SidebarItem' import variables from '@/styles/variables.scss' export default { components: { SidebarItem, Logo }, computed: { ...mapGetters([ 'permission_routes', //这里引入permission_routes 'sidebar' ]), activeMenu() { const route = this.$route const { meta, path } = route // if set path, the sidebar will highlight the path you set if (meta.activeMenu) { return meta.activeMenu } return path }, showLogo() { return this.$store.state.settings.sidebarLogo }, variables() { return variables }, isCollapse() { return !this.sidebar.opened } } } </script>
想看‘解决登录,退出后,显示的路由指向路径不变’的问题,可直接拉到后面的代码,修改一行代码就行啦
<template> <div class="navbar"> <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <breadcrumb class="breadcrumb-container" /> <div class="right-menu"> <el-dropdown class="avatar-container" trigger="click" > <div class="avatar-wrapper"> <img src="./favicon.png" class="user-avatar" > <i class="el-icon-caret-bottom" /> </div> <el-dropdown-menu slot="dropdown" class="user-dropdown" > <router-link to="/"> <el-dropdown-item v-if="user!=null"> <!-- 用户名:{{user}} --> 用户:{{ user.nickname }} </el-dropdown-item> </router-link> <a target="_blank" href="#" > <!-- <el-dropdown-item v-if="user!=null">账号:{{ user.userId }}</el-dropdown-item> --> </a> <a href="#"> <el-dropdown-item>状态:在线</el-dropdown-item> </a> <el-dropdown-item divided @click.native="logout" > <span style="display:block;">退出登录</span> </el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div> </template> <script> import { mapGetters } from 'vuex' import Breadcrumb from '@/components/Breadcrumb' import Hamburger from '@/components/Hamburger' import { getUser } from "@/utils/auth"; export default { components: { Breadcrumb, Hamburger }, computed: { ...mapGetters(['sidebar', 'avatar', 'name', 'account']) }, data () { return { user: {} } }, created () { this.getData() }, methods: { toggleSideBar () { this.$store.dispatch('app/toggleSideBar') }, async logout () { await this.$store.dispatch('user/logout') // this.$router.push(`/login?redirect=${this.$route.fullPath}`) //退出的时候 直接到登录界面,解决再次登录别的账号出现进去即404 this.$router.push(`/login`) }, getData () { this.user = JSON.parse(getUser()); } } } </script>
在router/index.js中,动态加载路由模块中的最后添加一下这行代码,相对的前面的一样的代码就删除了,还有方案一的代码也不必要存在了
export const asyncRoutes = [
{ path: "*", redirect: "/404", hidden: true } //把通配404页面放在动态路由的最底部,就不用在promission中设置了
]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。