一起来手撸一个管理后台vite+vue3--权限控制

5,136 阅读4分钟

前言

作为一个管理后台项目,权限控制是最基础的功能。这篇文章主要介绍了下页面级别的权限控制,即登录用户只能进入有权限的页面,菜单栏也只显示有权限的路径。下面来简单的说下实现思路:

  • 用户登入成功之后,获取后台给的登录凭证token
  • 在路由守卫beforeEach中判断是否获取了用户的权限列表
  • 获取成功后使用vue-routeraddRoute动态挂载有权限的路由
  • 根据权限列表,返回左侧菜单栏

准备工作

因为这个项目是个纯前端项目,项目中所有的请求接口都只能靠自己模拟了,我这里用mockjs文档模拟接口返回

  • 先安装 yarn add mockjs
  • 创建存放模拟数据文件
// src/api/mock/baseApi.js
import Mock from 'mockjs'
Mock.mock('/test/login', {
    code: 200,
    message: '成功',
    data: {
        'userName|1': Mock.Random.cname(),
        token: /\d{10,20}/,
    },
})
// ... other 

开始

路由改造

  • 在路由meta的配置中添加noAauth来判断当前路由是否需要进行权限认证,请注意:登录页面一定不要开启权限认证
// src/router/public.js
const routers = [{
    path: '/',
    meta: {noAauth: true },
    // ... other
}, {
    path: '/setting',
    // ... other
}, {
    path: '/login',
    meta: {noAauth: true},
    // ... other
}, ];
export default routers;
  • 挂载不需要权限认证的路由
// src/router/index.js
import publics from './public';
const routesList = [...publicst];
const routes = routesList.filter((i) => { // 过滤掉需要权限认证的路由
    return i.meta ? i.meta.noAauth : false;
});
const router = createRouter({ // 挂载不需要权限认证的路由
    history: createWebHashHistory(),
    routes,
});
// ... other

登入页面

QQ截图20220308152401.png

登录成功获取token

const login = async () => {
    if (active.value === 1) { // 手机号登录
        let res = await api.BaseApi.login(phone.value, verifCode.value)
        if (res.code === 200) {
            publicStore.setUserMsg(res.data) // 出发pinia中的action 缓存用户信息
            axios.defaults.headers.common['token'] = res.data.token; // 设置全局的请求token
            router.push('/') // 跳转到首页  如果首页也开启了权限认证 这里需要改成有权限的路径。
        } else {
            ElMessage({ type: 'error', message: res.message ? res.message : res })
        }
    } else { // 密码登录

    }
}

路由拦截

在上一步登录成功后跳转到首页,在跳转的过程中要获取用户的权限并动态加载路由

// src/router/index.js
import { PublicStore } from '@/store/Public';
// ... other
const getRoutesAuth = (power) => { // 添加路由
    let powerList = routesList.filter((i) => { // 过滤掉不需要权限的路由 因为已经挂载过一次
        return !i.meta || !i.meta.noAauth
    })
    powerList.forEach(e => {
        if (power === 1 || power.includes(e.path)) { //power(1)表示管理员权限全部挂载  判断权限列表是否包含当前路由 包含则挂载
            router.addRoute(e)
        }
    })
};
router.beforeEach(async(to, from, next) => {
    if (to.path !== '/login' && !localStorage.get('token')) { // 判断是否登录  非`login`页面无`token`的话跳转到登录页面
        next({ path: '/login' });
    } else {
        const stores = PublicStore() // pinia 状态管理
        if (to.path !== '/login' && stores.power.length === 0) { // 判断是否获取过权限
            let res = await stores.setPower() // 获取权限列表
            if (res) {
                getRoutesAuth(stores.power) // 根据权限列表挂载路由
                next({ path: to.fullPath }) // 跳转页面
            } else { // 获取权限失败返回到来源页面
                next({ path: from.path, query: { hasPower: 1 } })
            }
        } else if (router.hasRoute(to.name)) { // 判断路由是否存在
            next()
        } else { // 路由不存在返回原页面
            next({ path: from.path, query: { hasPower: 1 } })
        }
    }
});
export default router;

获取权限

piniasetPower中获取用户权限并生成左侧的菜单栏

// src/store/Public.js
import { defineStore } from 'pinia'
import api from '@/api'
//定义左边菜单列表
let menuList = [
    { router: '/', title: '首页', noAauth: true, icon: 'iconzhanghao', ch: [] },
    { router: '/adminSet', title: '用户管理', icon: 'iconzizhanghaoguanli', ch: [{ router: '/adminSet/accountSet', title: '权限配置' }] },
    { router: '/setting', title: '系统设置', icon: 'iconshezhi', ch: [] }
];
export const PublicStore = defineStore('Public', {
    state: () => {
        return {
            power: [],
            leftMenu: []
        }
    },
    actions: {
        async setPower() {
            let res = await api.BaseApi.getPower() // 获取权限
            if (res.code === 200) {
                this.power = res.data.length === 0 ? 1 : res.data // 权限列表返回为空 赋值为1表示拥有全部权限
                this.setLeftMenu()
                return true
            } else {
                return false
            }
        },
        setLeftMenu() { // 左边菜单栏
            let list = cloneDeep(menuList); // 深拷贝一份  防止不刷新时 权限改变后重新登入有问题
            if (this.power === 1) { // 超级管理员返回全部路由
                this.leftMenu = list;
            } else {
                // 设置已经添加的权限  `noAauth`true 表示无需进行权限控制
                list.forEach((i) => {
                    if (i.ch.length > 0) {
                        i.ch = i.ch.filter((y) => {
                            return this.power.includes(y.router) || y.noAauth; // 返回不需要权限控制和授予过权限的菜单
                        });
                        if (i.ch.length === 0) { // 如果子菜单为空 一级菜单置空
                            i.router = undefined
                        }
                    } else {
                        if (!this.power.includes(i.router) && !i.noAauth) { // 需要进行权限控制的未授权页面 
                            i.router = undefined // 一级菜单置空
                        }
                    }
                });
                this.leftMenu = list.filter((i) => { // 过滤掉没有权限的一级菜单
                    return !!i.router;
                });
            }

        }
    }
})

生成左侧菜单

获取 setLeftMenu设置的 leftMenu的值,循环生成菜单

// js
import { PublicStore } from '@/store/Public'
import { storeToRefs } from 'pinia'
const { leftMenu } = storeToRefs(PublicStore())
// vue
<el-menu>
    <template v-for="item in leftMenu" :key="item.router">
        <el-sub-menu v-if="item.ch && item.ch.length > 0" :index="item.router">
            <template #title>
                <i class="iconfont" :class="item.icon"></i>
                <span>{{ item.title }}</span>
            </template>
            <el-menu-item v-for="i in item.ch" :key="i.router" :index="i.router">{{ i.title }}</el-menu-item>
        </el-sub-menu>
        <el-menu-item v-else :index="item.router">
            <i class="iconfont" :class="item.icon"></i>
            <template #title>{{ item.title }}</template>
        </el-menu-item>
    </template>
</el-menu>

最后

以上的代码是部分核心代码,全部的代码戳这里 vueAdmin