vue-动态路由+按钮级权限(二)之动态路由
首先,把整个系统的路由表分为静态路由和动态路由,
静态路由为不需要权限就加载的路由组,
动态路由为权限关联路由
import addRoutes from './routers/add' import staticRoutes from './routers/static' export const routes = [ //...addRoutes, ...staticRoutes, ]
static.js
const routes = [ { path: "/", redirect: '/home', }, { path: '/home', name: 'home', meta:{ title:'首页', keepAlive: false, //此组件不需要被缓存 name:'home' }, component: () => import('@/views/home/main/index.vue'), }, { path: '/login', name: 'login', meta:{ keepAlive: false, }, component: () => import('@/views/login/main/index.vue') }, { path: '/404', name: '404', component: () => import('@/views/error/404.vue') }, // { // path: '/:pathMatch(.*)', // redirect: '/404' // } ] export default routes
add.js
[ { "path": "/personal", "name": "personal", "meta": { "title": "个人中心", "keepAlive": false, "name": "personal" }, "component": "personal" }, { "path": "/system", "name": "system", "meta": { "title": "系统设置", "keepAlive": false, "name": "system" }, "component": "system" }, { "path": "/multi-prjs", "name": "multi-prjs", "meta": { "title": "项目集成管理智慧运营中心", "keepAlive": false, "name": "multiPrjs" }, "component": "multiPrjs" }, { "path": "/single-prj", "name": "single-prj", "meta": { "title": "研发管理看板", "keepAlive": false, "name": "singlePrj" }, "component": "singlePrj" }, { "path": "/single-design", "name": "single-design", "meta": { "title": "设计看板", "keepAlive": false, "name": "singleDesign" }, "component": "singleDesign" }, { "path": "/single-dev", "name": "single-dev", "meta": { "title": "开发看板", "keepAlive": false, "name": "singleDev" }, "component": "singleDev" }, { "path": "/single-test", "name": "single-test", "meta": { "title": "个人中心", "keepAlive": false, "name": "singleTest" }, "component": "singleTest" }, { "path": "/single-ci", "name": "single-ci", "meta": { "title": "个人中心", "keepAlive": false, "name": "singleCi" }, "component": "singleCi" } ]
router/index
import Vue from 'vue' import { Message } from 'element-ui' import store from "../store"; import VueRouter from 'vue-router' import addRoutes from './routers/add' import staticRoutes from './routers/static' import db from '@/common/storage' //解决vue路由重复的时候,点击报错问题 const originalReplace = VueRouter.prototype.replace VueRouter.prototype.replace = function replace(location) { return originalReplace.call(this, location).catch(err => err) } const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => err) } Vue.use(VueRouter) export const routes = [ //...addRoutes, ...staticRoutes, ] const router = new VueRouter({ //mode:'history', mode: 'hash', base: process.env.BASE_URL, routes }) const whiteList = ['/login'] router.beforeEach(async(to, from, next) => { const hasLogin = window.sessionStorage.getItem('token') || false if (hasLogin) { if (to.path === '/login') { next({ path: '/' }) }else{ if (to.name === 'home') { store.state.singlePrjStore.xname = '创新中心'; store.state.singlePrjStore.orgStoreId = null; store.state.singlePrjStore.projectId = null; store.state.singlePrjStore.projectNo = null; store.state.singlePrjStore.iterationId = null; store.state.singlePrjStore.iterationStartTime = null; store.state.singlePrjStore.iterationEndTime = null; } const hasRoles = store.state.userStore.asyncRoutes.length > 0 if(hasRoles){ next() }else{ try { let params ={ userId:db.ss.get('userId') } const accessRoutes = await store.dispatch('userStore/generateRoutes',params) router.addRoutes(accessRoutes) next({ ...to, replace: true }) } catch (error) { db.ss.clear(); next(`/login?redirect=${to.path}`) } } next() } } else { if (whiteList.indexOf(to.path) !== -1) { // 访问的路径处于白名单中 next() } else { Message({ message: '您尚未登录,你先登录', type: 'error', duration: 5 * 1000 }) // 没有登录,跳转登录页 next(`/login?redirect=${to.path}`) } } }) export default router
store/userStore
import {getSysUserInfo,getSysUserPermissions} from '@/server/user/user' import formatRoutes from '@/plugins/analysisRoute' import db from '@/common/storage' const userStore = { namespaced:true, state: { asyncRoutes:[], userInfo:{ roles:[] }, }, actions: { async getUserInfo({ commit },params) { const { code, data } = await getSysUserInfo(params); if (code == 0) { commit('setUserInfo', data) } }, async generateRoutes({ commit },params) { return new Promise((resolve) => { getSysUserPermissions(params).then(res=>{ let accessedRoutes = formatRoutes(res.data.menuList); commit('setAsyncRoutes', accessedRoutes); db.ss.set('permissionList',res.data.buttonLit) resolve(accessedRoutes) }); }) } }, mutations: { setAsyncRoutes(state, data) { state.asyncRoutes = data; }, setUserInfo(state, data) { db.ss.set('userInfo',data) state.userInfo = data; } } }; export default userStore;
db.js
/** * localStorage and sessionStorage basic operation */ const ls = localStorage; const ss = sessionStorage; const db = { ls: { get(key) { try { return JSON.parse(ls.getItem(key)); } catch (err) { return ls.getItem(key); } }, set(key, value) { ls.setItem(key, JSON.stringify(value)); }, remove(key) { ls.removeItem(key); }, clear() { ls.clear(); } }, ss: { get(key) { try { return JSON.parse(ss.getItem(key)); } catch (err) { return ss.getItem(key); } }, set(key, value) { ss.setItem(key, JSON.stringify(value)); }, remove(key) { ss.removeItem(key); }, clear() { ss.clear(); } } }; export default db;
formatRoutes.js
function loadView(component) { return (resolve) => require([`@/views/${component}/main/index`], resolve) } export default function formatRoutes(routes, Layout) { const formatRoutesArr = [] routes.forEach(route => { const router = { meta: {} } const { pid, path, redirect, component, icon, name, meta, children } = route if (component === 'Layout') { router['component'] = Layout } else { router['component'] = loadView(component) } if (redirect !== null) { router['redirect'] = redirect } if (icon !== null) { router['meta']['icon'] = icon } if (children && children instanceof Array && children.length > 0) { router['children'] = formatRoutes(children) } if (name !== null) { router['name'] = name } // router['meta']['title'] = meta.title // router['meta']['name'] = meta.name //router['meta']['keepAlive'] = meta.keepAlive router['path'] = path if (pid === 0) { router['alwaysShow'] = true } formatRoutesArr.push(router) }) // 将404页面添加到最后面 formatRoutesArr.push({ path: '*', redirect: '/404', hidden: true }) return formatRoutesArr }
后台返回的权限列表接口数据结构为
总结:登录过后通过上面接口存到vuex里的asyncRoutes数组有没有值决定路由拦截器的逻辑,需要注意的是
加入动态路由会出现404的问题
原因在于,我们在addRoutes加动态路由前,就配置了通配符404路由;
所以上图静态路由表里面的注释那边就是重点
2>需要在退出登录,以及token失效过期清除asyncRoutes
退出登录
...mapMutations("userStore", ["setAsyncRoutes"]),
loginOut() { this.$message({ message: "退出登录成功,跳转至登录页...", type: "success", }); this.$db.ss.clear(); this.setAsyncRoutes([]); setTimeout(() => { this.$router.push("/login"); }, 1000); },
响应拦截器里面的
import store from "@/store";
store.state.userStore.asyncRoutes = [];