前端权限管理

该博客详细介绍了如何在Vue项目中实现权限管理,包括初始化路由、全局路由导航守卫、封装网络请求、状态管理和路由动态加载。通过用户权限数据与预定义路由对比,筛选出用户可访问的路由,并动态添加到路由配置中,同时实现菜单的动态生成。此外,还涉及了登录逻辑、模拟后端数据和解决本地跨域问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义好全部的路由地址(需要权限校验)
通过用户不同向后台请求不同的用户权限数据
对用户权限做对比:请求数据 与 全部的路由对比,筛选后取出来作为路由配置
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': ''
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值