vue-element-admin动态路由权限、菜单

文章介绍了在一个Vue3项目中,如何实现基于VueRouter的路由权限控制,包括全局前置守卫、动态路由生成以及使用Vuex管理状态。主要涉及未登录用户的重定向、登录后动态获取并添加路由、以及利用localStorage存储用户信息和权限数据。
摘要由CSDN通过智能技术生成

前言

首先我对于vue和语法糖不熟练,使用的应该是vue3,但具体并不明白vue2和vue3的区别;在此分享我的package.json

{
  "name": "vue3_cli_default",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "2": "^3.0.0",
    "axios": "^1.3.2",
    "element-plus": "^2.2.29",
    "fast-glob": "^3.2.12",
    "qs": "^6.11.0",
    "vue": "^3.2.8",
    "vue-router": "^4.1.6",
    "vue-wechat-title": "^2.0.7",
    "vuex": "^4.0.2"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.0",
    "@vue/compiler-sfc": "^3.2.6",
    "vite": "^2.5.2",
    "vite-plugin-svg-icons": "^2.0.1"
  }
}

1、router/index.js

在此配置不需要验证的路由:如login、404;将不需要验证的路由放在constRouter中

import {
    createRouter,
    createWebHashHistory
} from 'vue-router' //引入vue-router

import store from '../store/index.js'

// 通用页面,这里的配置不需要权限
export const constRouter = [{
    path: '/login',
    name: 'login',
    component: () =>
        import ('@/views/login/index.vue'),
    meta: {
        title: '登录'
    }
}, {
    path: '/404',
    name: '404',
    component: () =>
        import ('../views/error/404.vue'),
    meta: { title: "404" }
}, {
    path: '/*',
    redirect: '/404'
}, {
    path: '/',
    redirect: '/home/index', //首页
}]


const router = createRouter({
    history: createWebHashHistory(),
    routes: constRouter
})


export default router

2、router/permission.js

在permission.js这个文件中处理前置路由守卫的功能。

(如果文字逻辑不清晰请忽略,仅为初步的个人理解)

此时的主要逻辑为:缓存中有token等数据时-->判断store中是否有从后端调起的路由信息数据(addRoutes)-->store中addRoutes没有数据;调用store中的函数去处理从后端调起的路由信息数据

未登录时的逻辑:进入login页面;进行登录操作

输入密码后的逻辑:通过store的user.js处理从后端获取的token、动态路由数据;并进行处理操作(后续有详细代码

// 路由的全局首位

// 权限控制逻辑
import router from './index'
import store from '../store/index'

import { ElMessage } from 'element-plus' //导入消息提示
import { getUser, getRoutes } from '@/utils/auth' // 从cookie获取令牌

const whiteList = ['/login'] //排除的路径

router.beforeEach(async(to, from, next) => {
    if (to.path === '/login' && !getRoutes()) {
        // 没有菜单权限数据&去登录页-->路由放行
        // 清空store的路由数据
        await store.dispatch('permission/generateRoutes', [])
        next()
    } else {
        // 判断用户是否已登录
        const hasToken = getUser()
        if (hasToken) {
            try {
                if (!store.getters.user) {
                    store.dispatch('user/setInfo', hasToken)
                }
                // 判断是否进行路由过滤
                if (store.getters.addRoutes.length <= 0) {
                    const roles_routes_lo = getRoutes()

                    // 获取动态路由数据
                    const accessRoutes = await store.dispatch('permission/generateRoutes', roles_routes_lo)
                    console.log(accessRoutes, "accessRoutes-获取动态路由数据", store.getters.addRoutes)

                    // 添加这些路由至路由器
                    router.addRoute(accessRoutes)

                    next({...to, replace: true })
                } else {
                    // 已经进行路由过滤-->放行
                    next() //继续即可
                }
            } catch (error) {
                // 捕获异常-->主要是获取用户信息的异常,此处是必要的,返回数据异常极容易造成路由过滤问题,
                console.log('add permission error', error)

                // // 出错需要重置令牌并重新登陆(令牌过期,网络错误等原因)
                await store.dispatch('user/resetToken')
                ElMessage({
                    showClose: true,
                    message: error || "网络错误-beforeEach",
                    type: 'error',
                })
                next(`/login?redirect=${to.path}`)
            }
        } else {
            // 用户无令牌
            if (whiteList.indexOf(to.path) !== -1) {
                //白名单路由放过
                next()
            } else {
                // 重定向至登录页
                next(`/login?redirect=${to.path}`)
            }
        }

    }
})

3、main.js

在此处增加路由前置守卫文件,重点是:import './router/permission'

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// router路由
import router from './router/index.js'
app.use(router)

// 路由标题
import VueWebchatTitle from 'vue-wechat-title'
app.use(VueWebchatTitle)

// elementUi
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)

// vux
import Vuex from 'vuex'
app.use(Vuex)

// // axios
// import axios from 'axios'
// app.use(axios)

// store
import store from './store/index.js'
app.use(store)

// 全局路由守卫
import './router/permission'

// 导入svg图片插件,可以在页面上显示svg图片
import 'virtual:svg-icons-register'
import SvgIcon from './components/SvgIcon.vue'
app.component('svg-icon', SvgIcon)

app.mount('#app')

4、utils/auth.js

这里处理的是获取缓存中的user、routes等信息

const UserKey = 'user';
const RoutesKey = 'anxinRoutes'
const BtnKey = 'anxinBtn'

// 获取用户信息
export function getUser() {
    return JSON.parse(window.localStorage.getItem(UserKey))
}
// 添加用户信息
export function setUser(userInfo) {
    return window.localStorage.setItem(UserKey, JSON.stringify(userInfo))
}

export function removeStorage(key) {
    // 清除特定键值对
    return window.localStorage.removeItem(key);
}

// 获取菜单权限
export function getRoutes() {
    return JSON.parse(window.localStorage.getItem(RoutesKey))
}
// 添加菜单权限
export function setRoutes(routes) {
    return window.localStorage.setItem(RoutesKey, JSON.stringify(routes))
}

5、store/index.js

import { createStore } from 'vuex'

import permission from './modules/permission'
import user from './modules/user'

export default createStore({
    state: {},
    mutations: {},
    actions: {},
    modules: {
        permission,
        user
    },
    // 定义全局getters 方便访问user 模块的数据
    getters: {
        user: state => state.user.user,
        addRoutes: state => state.permission.addRoutes,//仅需要加权限设置的路由数据
        roles_routes: state => state.permission.roles_routes,//所有路由数据
        menuRoutes: state => state.permission.menuRoutes,//在页面中渲染显示的菜单数据
    }
})

6、store/modules/permission.js

在此处把路由数据处理为自己需要的数据格式,

从后端获取的路由数据:

 

我需要的数据格式为:

 store/modules/permission.js处理数据格式

// 权限管理模块
import { constRouter } from '@/router'
import { getBtn } from '@/utils/auth'


const state = {
    roles_routes: [], //完整路由表
    addRoutes: [], //用户可访问路由表
    menuRoutes: [], //菜单数据
}

const mutations = {
    SET_ROUTES: (state, routes) => {
        // routes 用户可以访问的权限
        state.addRoutes = routes

        // 完整的路由表
        state.roles_routes = constRouter.concat(routes)
    },
    SET_MENU_ROUTES: (state, routes) => {
        // 菜单数据
        state.menuRoutes = routes
    },
}

const actions = {
    generateRoutes({ commit }, asyncRoutes) {
        return new Promise(resolve => {
            let accessedRoutes;
            accessedRoutes = asyncRoutes || []

            // 需要处理路由数据,在此加函数处理
            var last_routes = []
            if (accessedRoutes.length != 0) {
                last_routes = arrToMenu(accessedRoutes)
            }

            commit('SET_ROUTES', last_routes) //添加用户可访问的路由表
            commit('SET_MENU_ROUTES', accessedRoutes) //添加菜单数据

            resolve(last_routes)
        })
    }
}

// 处理菜单数据
export function arrToMenu(routes) {
    var nodes = {
        path: '/',
        component: eval(`() => import('../../views/layout/index.vue')`),
        children: [],
    }
    var res = []
    for (var i = 0; i < routes.length; i++) {
        const row = routes[i]
        var child = {}
        if (!row.children) {
            child = {
                path: row.path,
                name: row.name,
                component: eval(`() => import('../../views${row.path}.vue')`),
                meta: {
                    title: row.name,
                    icon: row.source
                },
            }
            res.push(child)

        } else {
            for (var j = 0; j < row.children.length; j++) {
                var row_child = row.children[j]
                child = {
                    path: row_child.path,
                    name: row_child.name,
                    component: eval(`() => import('../../views${row.path}${row_child.path}.vue')`),
                    meta: {
                        title: row_child.name,
                        icon: row_child.source
                    },
                }
                res.push(child)
            }
        }
    }
    nodes.children = res
        // console.log(nodes, 'nodes')
    return nodes;
}

export default {
    namespaced: true,
    state,
    mutations,
    actions
}

7、login.vue

<template>
	<div class="login-main">
		<div class="login-box">
			<div class="login-header">
				<img src="../../assets/logo.png" class="login-logo">
			</div>
			<el-form
				ref="ruleFormRef"
				:rules="formRule"
				:model="formVal"
			>
				<el-form-item label="账号" prop="account">
					<el-input v-model="formVal.account" placeholder="请输入账号" />
				</el-form-item>
				<el-form-item label="密码" prop="password">
					<el-input v-model="formVal.password" placeholder="请输入密码" />
				</el-form-item>
				<el-form-item>
					<el-button type="primary" @click="onSumbit" :loading="loading">登录</el-button>
				</el-form-item>
			</el-form>
		</div>
	</div>
</template>

<script>
	import { login,menutreeList } from '@/utils/api/user.js'
	export default{
		data(){
			return {
				formVal:{account:'',password:'',},
				formRule:{
					account: [
						{ required: true, message: '请输入账号', trigger: 'blur' },
					],
					password: [
						{ required: true, message: '请输入密码', trigger: 'blur' },
					],
				},
				loading: false, //登陆状态
			}
		},
		methods: {
			// 登录功能
			async onSumbit () {
				var _this = this
				try {
				    // 1、表单验证
				    await this.$refs.ruleFormRef.validate()
					this.loading = true
				    // 2、请求
				    const { data } = await login(_this.formVal)
					// 处理菜单栏数据
					_this.login(data)
					_this.$message.success(data.msg)
				} catch (err) {
					this.loading = false
					console.log('验证失败', err)
				}
			},
			login(data){
                // 在此处理数据
				this.$store
					.dispatch('user/login',data)
					.then(()=>{
						this.loading = true
						// 登陆成功后重定向
						this.$router.push({
							path: this.$route.query.redirect || '/'
						})
					})
					.catch(err=>{
						this.loading = true
						console.log(err)
					})
			}
		}
	}
</script>

<style scoped>
	.login-main{
		width: 100vw;
		height: 100vh;
		display: flex;
		align-items: center;
		justify-content: center;
		background-image: url('../../assets/bg1.jpg');
		background-position: right bottom;
		background-repeat: no-repeat;
		background-size: cover;
	}
	.login-box{
		width: 300px;
		height: 200px;
		border-radius: 5px;
		box-shadow: 10px 10px 5px rgba(0, 0, 0, 0.3);
		background-color: #fff;
		display: flex;
		flex-direction: column;
		padding: 2rem;
		align-items: center;
		justify-content: center;
		
	}
	.login-header{
		padding-bottom: 20px;
	}
	.login-logo{
		width: 70px;
		height: 70px;
		border-radius: 50%;
		box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.3);
	}
	.el-button{
		width: 100%;
	}
</style>

8、store/modules/user.js

在此处理登陆后获取的数据,将其存入缓存中;然后跳转页面会自动触发路由守卫

import { setUser, removeStorage, setRoutes, setBtn } from '@/utils/auth'

// 村赤用户令牌和角色信息
const state = {
    user: {}, //用户名、token等
}

const mutations = {
    SET_USERINFO: (state, userInfo) => {
        state.user = userInfo;
    }
};

const actions = {
    // 用户登录
    login({ commit }, userInfo) {
        const { data } = userInfo;
        return new Promise((resolve, reject) => {
            var userInfo = {
                accessToken: data.accessToken,
                refreshToken: data.refreshToken,
                userId: data.userId
            }

            // 保存状态
            commit('SET_USERINFO', userInfo)

            // 写入cookie(token等、user,anxinRoles)
            setUser(userInfo)
            setRoutes(data.menuVoList)

            resolve()
        })
    },
    // 获取用户角色信息
    setInfo({ commit }, user) {
        return new Promise((resolve) => {
            commit('SET_USERINFO', user)
            resolve(user)
        })
    },
    // 重置令牌
    resetToken({ commit }) {
        return new Promise(resolve => {
            commit('SET_USERINFO', {})
            removeStorage('user')
            removeStorage('anxinRoutes')
            resolve()
        })
    }
}

export default {
    namespaced: true,
    state,
    mutations,
    actions
}

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值