流程
1.首次加载->跳转根页面/home->前置守卫拦截->没有保存token跳转登录页->守卫拦截->白名单放行
2.点登录保存token->跳转到根路径->守卫拦截->有token尝试获取角色->没有角色去获取角色并保存->常规路由数组添加一个有角色权限配置的数组->继续跳转->守卫拦截->有角色->最终跳转到根路径
3.刷新页面->缓存有token->有token尝试获取角色->跟上面流程一样
创建router/index.js
import Vue from "vue";
import Router from "vue-router";
import Layout from '@/layout'; // 布局页
Vue.use(Router);
// 通用页面:不需要守卫,可直接访问
export const constRoutes = [
{
path: "/login",
component: () => import("@/views/Login"),
hidden: true // 导航菜单忽略该项
},
{
path: "/",
component: Layout,// 应用布局
redirect: "/home",
children: [
{
path: "home",
component: () =>
import(/* webpackChunkName: "home" */ "@/views/Home.vue"),
name: "home",
meta: {
title: "Home", // 导航菜单项标题
icon: "qq" // 导航菜单项图标
}
}
]
}
];
// 权限页面:受保护页面,要求用户登录并拥有访问权限的角色才能访问
export const asyncRoutes = [
{
path: "/about",
component: Layout,
redirect: "/about/index",
children: [
{
path: "index",
component: () =>
import(/* webpackChunkName: "home" */ "@/views/About.vue"),
name: "about",
meta: {
title: "About",
icon: "qq",
roles: ['admin', 'editor']
},
}
]
}
];
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: constRoutes
});
创建layouts/index.vue 这个页面其实没什么用 实际跳转到的是children里面/home
<template>
<div class="app-wrapper">
<!-- <sidebar class="sidebar-container" /> -->
<div class="main-container">
<router-view />
</div>
</div>
</template>
views/home.vue
<template>
<div class="home">
<div>冲啊,手榴弹扔了{{$store.state.count}}个</div>
<!-- <div>冲啊,手榴弹扔了{{$store.state.a.count}}个</div> -->
<button @click="add">扔一个</button>
<button @click="asyncAdd">蓄力扔一个</button>
<img alt="Vue logo" src="../assets/logo.png">
<!-- <HelloWorld msg="Welcome to Your Vue.js App"/> -->
</div>
</template>
<script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'home',
components: {
//HelloWorld
},
methods: {
add() {
},
asyncAdd() {
}
}
}
</script>
创建store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import permission from './modules/permission'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {user, permission}
})
export default store
创建store/modules/user.js
// 用户状态:登录态、信息
const state = {
token: localStorage.getItem("token"),
// 其他用户信息
roles: [],
};
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token;
},
SET_ROLES: (state, roles) => {
state.roles = roles;
},
};
const actions = {
// 模拟用户登录
login({ commit }, userInfo) {
const { username } = userInfo;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username === "admin" || username === "jerry") {
commit("SET_TOKEN", username);
localStorage.setItem("token", username);
resolve();
} else {
reject("用户名、密码错误");
}
}, 1000);
});
},
getInfo({ commit, state }) {
return new Promise(resolve => {
setTimeout(() => {
const roles = state.token === "admin" ? ["admin"] : ["editor"];
commit("SET_ROLES", roles);
resolve({ roles });
}, 1000);
});
},
resetToken({ commit }) {
return new Promise(resolve => {
commit("SET_TOKEN", "");
commit("SET_ROLES", []);
localStorage.removeItem("token");
resolve();
});
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};
创建store/modules/permission.js
// 1.保存路由表
// 2.可以过滤出当前用户可访问的路由表
import { asyncRoutes, constRoutes } from "@/router";
const state = {
routes: [], // 完整路由表
addRoutes: [] // 用户可访问路由表
};
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes;
state.routes = constRoutes.concat(routes);
}
};
const actions = {
// 路由生成:在得到用户角色后会第一时间调用
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
// 根据角色做过滤处理
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);;
commit("SET_ROUTES", accessedRoutes);
resolve(accessedRoutes);
});
}
};
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;
}
function hasPermission(roles, route) {
// 如果当前路由有roles字段则需判断用户访问权限
if (route.meta && route.meta.roles) {
// 若用户拥有的角色中有被包含在待判定路由角色表中的则拥有访问权
return roles.some(role => route.meta.roles.includes(role));
} else {
// 没有设置roles则无需判定即可访问
return true;
}
}
export default {
namespaced: true,
state,
mutations,
actions
};
创建views/Login.vue
<template>
<div>
<h2>用户登录</h2>
<div>
<input type="text" v-model="username" />
<button @click="login">登录</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: "admin"
};
},
methods: {
login() {
this.$store
.dispatch("user/login", { username: this.username })
.then(() => {
this.$router.push({
path: this.$route.query.redirect || "/"
});
})
.catch(error => {
alert(error);
});
}
}
};
</script>
根目录创建permission.js
import router from "./router";
import store from "./store";
const whiteList = ["/login"]; // 无需令牌白名单
// 全局守卫
router.beforeEach(async (to, from, next) => {
// 获取令牌判断用户是否登录
const hasToken = localStorage.getItem("token");
// 已登录
if (hasToken) {
if (to.path === "/login") {
// 若已登录没有必要显示登录页,重定向至首页
next({ path: "/" });
} else {
// 去其他路由,暂时放过
// 接下来执行用户角色逻辑, todo
const hasRoles =
store.state.user.roles && store.state.user.roles.length > 0;
if (hasRoles) {
next();
} else {
// 请求角色信息
try {
const { roles } = await store.dispatch("user/getInfo");
// 动态路由生成,todo
const accessRoutes = await store.dispatch(
"permission/generateRoutes",
roles,
);
// 动态追加到router
router.addRoutes(accessRoutes);
// 路由在进来一次
next({ ...to });
} catch (error) {
// 出错需重置令牌并重新登录(令牌过期、网络错误等原因)
await store.dispatch("user/resetToken");
next(`/login?redirect=${to.path}`);
alert(error || "未知错误");
}
}
}
} else {
// 未登录
if (whiteList.indexOf(to.path) !== -1) {
// 白名单中路由放过
next();
} else {
// 重定向至登录页
next(`/login?redirect=${to.path}`);
}
}
});
在main.js引入
import './permission'