定义好全部的路由地址(需要权限校验)
通过用户不同向后台请求不同的用户权限数据
对用户权限做对比:请求数据 与 全部的路由对比,筛选后取出来作为路由配置
1、初始化路由
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from "../pages/login"
import Home from "../pages/home"
import NotFound from "../pages/errorPage/404"
import Forbidden from "../pages/errorPage/403"
import Layout from "../pages/layout"
Vue.use(VueRouter)
// 初始化路由
const routes = [
{
path: '/login',
name: 'Login',
component: Login
}
]
// 准备动态加载的路由 在获取后台返回的数据后动态添加路由
// 之后做法:初始化路由 let initialRoutes = router.options.routes;
// 把这个准备动态加载的路由加进去 router.addRoutes(DynamicRoutes);
// 存到vuex 中 能够在菜单栏中体现:
//commit("SET_PERMISSION",[ ...initialRoutes , ...DynamicRoutes]) 不是在这里实现的
export const DynamicRoutes = [
{
path:"",
component:Layout,
name:'container',
redirect:"home",
meta:{
requiresAuth:true,
name:"首页"
},
children:[
{
path:"home",
component:Home,
name:"home",
meta:{
// 去进行 匹配规则
name:"首页",
icon:"icon-name"
}
}
]
},
{
path:"/403",
component:Forbidden
},
{
path:"*",
component:NotFound
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
2、全局路由导航守卫
路由权限处理
router/permission.js
import router from "./index"
import store from "../store/index"
router.beforeEach((to,from,next) =>{
if(!store.state.UserToken){
// 未登录 页面是否需要登录
if(to.matched.length > 0 && !to.matched.some(record => record.meta.requiresAuth)){
next();
}else{
next({
path:"/login"
})
}
}else{
// 用户已经登录 处理路由的访问权限
if(!store.state.permission.permissionList){
store.dispatch("permission/FETCH_PERMISSION").then(() =>{
next({
path:to.path
})
})
}else{
// store存在权限
if(to.path !== "/login"){
next();
}else{
next(from.fullPath)
}
}
}
})
3、封装网络请求方法
api/index.js
import axios from "../utils/http"
import store from "../store"
//网络请求
export function login(user){
return axios.get("/api/login?user=" + user)
}
export function fetchPermission(){
return axios.get("/api/permission?user=" + store.state.UserToken);
}
4、封装axios
请求拦截和响应拦截
utils/http.js
import axios from 'axios'
// import store from '@/store/index.js'
import baseURL from './baseURL'
import { Message } from 'element-ui'
const http = {}
var instance = axios.create({
timeout: 5000
})
// 添加请求拦截器
instance.interceptors.request.use(
function(config) {
// 请求头添加token
// if (store.state.UserToken) {
// config.headers.Authorization = store.state.UserToken
// }
return config
},
function(error) {
return Promise.reject(error)
}
)
// 响应拦截器即异常处理
instance.interceptors.response.use(
response => {
return response.data
},
err => {
return Promise.reject(err.response)
}
)
http.get = function(url, options) {
return new Promise((resolve, reject) => {
instance
.get(url, options)
.then(response => {
if (response.code === 0) {
resolve(response.data)
} else {
Message.error({
message: response.message
})
reject(response.message)
}
})
.catch(e => {
console.log(e)
})
})
}
http.post = function(url, data, options) {
return new Promise((resolve, reject) => {
instance
.post(url, data, options)
.then(response => {
if (response.code === 0) {
resolve(response.data)
} else {
Message.error({
message: response.message
})
reject(response.message)
}
})
.catch(e => {
console.log(e)
})
})
}
export default http
5、store状态管理
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from "./defaultState"
import mutations from "./mutations"
import modules from "./modules"
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
modules
})
store/modules/index.js 封装不同模块后统一放到这里
import permission from "./permission"
//可能还有其他模块的:
// import xxx from "xxx"
export default {
permission,
// xxx
}
store/modules/permission.js 封装的某一模块
import { fetchPermission } from "../../api/index"
//DynamicRoutes 准备动态加载的路由
import router,{ DynamicRoutes } from "../../router/index"
// dynamicRouter 定义好的所有需要权限验证的路由列表
import dynamicRouter from "../../router/dynamic-router"
import { recursionRouter,setDefaultRoute } from "../../utils/recursion-router"
export default {
namespaced:true,
state:{
permissionList:null,
sidebarMenu:[],// 导航菜单
currentMenu:'' // 高亮
},
getters:{},
mutations:{
// routes 是 DynamicRoutes 动态加载的路由 传来的
SET_PERMISSION(state,routes){
state.permissionList = routes;
},
CLEAR_PERMSSION(state){
state.permissionList = null;
},
SET_MENU(state,menu){
state.sidebarMenu = menu;
},
CLEAR_MENU(state){
state.sidebarMenu = []
}
},
// 异步访问 权限管理
actions:{
async FETCH_PERMISSION({ commit,state }){
//fetchPermission是封装的网络请求方法 拿到数据permissionList
let permissionList = await fetchPermission();
// 筛选 符合条件的路由
//dynamicRouter 定义好的所有需要权限验证的路由列表
let routes = recursionRouter(permissionList,dynamicRouter);
//要把符合的路由添加到 准备动态加载的路由DynamicRoutes
let MainContainer = DynamicRoutes.find(v => v.path === "");
//因为要当到children 作为子路由 获取children
let children = MainContainer.children;
//将符合条件的路由添加到children
children.push(...routes)
// 生成菜单 store中
commit("SET_MENU",children);
// 设置默认路由
setDefaultRoute([MainContainer]);
// 初始化路由
let initialRoutes = router.options.routes;
// 把这个准备动态加载的路由加进去
router.addRoutes(DynamicRoutes);
// 存到vuex 中 能够在菜单栏中体现
commit("SET_PERMISSION",[ ...initialRoutes , ...DynamicRoutes])
}
}
}
各个模块共用的state
store/defaultState.js
把它都导入到store/index.js 中使用的
export default {
//本地存储 用户下次登录是否需要 直接取
//每个对象都有get set 属性 当获取属性时 触发 get ,当修改属性时触发set
get UserToken(){
return localStorage.getItem('token');
},
set UserToken(value){
localStorage.setItem('token',value)
}
}
各个模块共用的
store/mutations (这是对state 进行操作修改,也就是修改它时同时也会触发state中的set)
把它都导入到store/index.js 中使用的
登录时就把 token存到vuex中
export default {
//在登录的时候通过this.$store.commit('LOGIN_IN',token); 将token存到vuex
LOGIN_IN(state,token){
state.UserToken = token;
},
LOGIN_OUT(state){
state.UserToken = ""
}
}
6、比对路由权限
utils/recursion-router.js
//方法一: 比对路由权限
//userRouter 后台返回的路由权限json 数组形式
//allRouter 前端配置好的路由权限数据(全部需要权限验证的) 数组形式
// realRoutes 过滤之后的符合条件的路由
export function recursionRouter(userRouter = [],allRouter = []){
var realRoutes = [];
allRouter.forEach((v,i) =>{
userRouter.forEach((item,index) =>{
// 有这个权限
if(item.name === v.meta.name){
//继续比对 子元素
if(item.children && item.children.length > 0){
//有子元素 递归
v.children = recursionRouter(item.children,v.children);
}
realRoutes.push(v)
}
})
})
return realRoutes;
}
//指定返回的默认路由
export function setDefaultRoute(routes){
routes.forEach((v,i) =>{
if(v.children && v.children.length > 0){
v.redirect = { name : v.children[0].name}
setDefaultRoute(v.children);
}
})
}
7、登录请求:
//引入封装的网络请求方法
import { login } from "../../api"
methods: {
async login() {
// 网络请求 传入参数this.account 来源于用户的表单输入v-model="account"
let data = await login(this.account);
//请求后端数据返回的data 中含有后台服务器生成的token
let token = data.token;
// 本地 vuex
this.$store.commit('LOGIN_IN',token);
this.$router.replace("/")
}
},
8、模拟后端向前端返回数据
mock/index.js
//安装express 使用
const express = require("express");
const app = express();
//json文件是模拟后台需要向前端返回的json数据
const vipLogin = require("./data/vip_login.json");
const adminLogin = require("./data/admin_login.json");
const adminPermission = require("./data/admin_permission.json");
const vipPermission = require("./data/vip_permission.json");
const url = require("url");
//模拟后台 向前端 返回数据
app.get("/login", (req, res) => {
const user = url.parse(req.url, true).query.user;
if (user === 'admin') {
res.send(adminLogin)
} else {
res.send(vipLogin)
}
})
//模拟后台 向前端 返回数据
app.get("/permission", (req, res) => {
const user = url.parse(req.url, true).query.user;
if (user === 'admin') {
res.send(adminPermission)
} else {
res.send(vipPermission)
}
})
//监听端口
app.listen(3300, () => {
console.log('服务器运行在3300');
})
9、本地跨域问题
(一般前端开发模式使用)
vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
//跨域访问的后端地址
target: 'http://localhost:3300',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}