vue-权限控制

最近因为失业了,有了挺多时间的,决定写一些有关前端的技术点,希望能对还从事前端开发的人有所帮助,也给自己从事前端画上一个句号吧。vue权限控制方面有前端路由和后端路由,前端路由一般所有的路由信息都是在前端路由文件中,判断不同的用户展示不同的路由信息,后端路由是不同的用户的路由信息都是后端通过接口返回,前端将每个不同登录用户的路由信息添加到路由中,进而实现不同用户不同路由不同菜单不同权限,第一中方式实现相对简单,目前企业大部分vue项目都使用的是第二种方式,所以我这里要对第二种方式进行详细讲解

案例中使用的一些前端技术

前端:vue3、element-plus、js-cookie(存储cookie)、pinia、vue-router、vite、sass

服务端:node.js、express、cors(解决跨域)

通过上述构建:

8423eb9d638c4e66bf560cb4c13eba44.png

node.js服务的目录结构

3d6ba174f4354726973f7f04105526b0.png

server.js

const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());
app.use(express.json())

app.post('/login', (req, res) => {
    let username = req.body.username;
    let password = req.body.password;
    if (username === 'admin' && password === '12345') {
        res.json({
            id: 1,
            token: 'asbddfghhhhhss',
        })
    } else if (username === 'user1' && password === '22222') {
        res.json({
            id: 2,
            token: 'dsfldsjljlj',
        })
    } else {
        res.json({
            mes: '用户名密码错误'
        })
    }
})

app.get('/buttonPower', (req, res) => {
    let id = req.query.id;
    if (id === '1') {
        res.json([
            '1000',
            '1001',
            '1002',
            '1003'
        ])
    } else if (id === '2') {
        res.json([
            '1004',
            '1005',
            '1006',
            '1007'
        ])
    }
})

app.get('/routes', (req, res) => {
    let id = req.query.id;
    if (id === '1') {
        res.json([
            {
                path: '/',
                name: 'page1',
                component: 'page1',
                children: []
            },
            {
                path: '/page2',
                name: 'page2',
                component: '',
                children: [
                    {
                        path: '/page3',
                        name: '文件a',
                        component: 'page3',
                    },
                    {
                        path: '/page4',
                        name: '文件b',
                        component: 'page4',
                    }
                ]
            },
            {
                path: '/page5',
                name: 'page5',
                component: '',
                children: [
                    {
                        path: '/page6',
                        name: '文件c',
                        component: 'page6',
                    },
                    {
                        path: '/page7',
                        name: '文件d',
                        component: 'page7',
                    }
                ]
            }
            //name 值相同导致前端to.matched为空哈哈
            // {
            //     path: '/page3',
            //     name: 'page3',
            //     component: 'page3'
            // },
            // {
            //     path: '/page4',
            //     name: 'page4',
            //     component: 'page4'
            // }
        ])
    } else if (id === '2') {
        res.json([
            {
                path: '/',
                name: 'page1',
                component: 'page1'
            },
            {
                path: '/page2',
                name: 'page2',
                component: 'page2'
            },
            {
                path: '/page4',
                name: 'page4',
                component: 'page4'
            }
        ])
    }
})

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

 vue目录结构

9816d21dae294cb1a748372d804bb369.png

db317d90fa1f499cb640557b7c64b3ac.png

vue的权限控制:页面权限控制(根据用户的权限动态的决定路由规则)、按钮权限控制(根据用户权限显示或隐藏按钮)

页面权限控制流程

用户登录

//提交表单引用的函数
const submitForm = () => {
    //校验表单
    loginFormRef.value.validate((valid) => {
        if (valid) {
            axios.post("http://127.0.0.1:3000/login", loginForm).then((res) => {
                if (res.data.id) {
                    Cookie.set('id', res.data.id); //存储id
                    Cookie.set('token', res.data.token); //存储token
                    let buttonPinia = useButton();
                    buttonPinia.getButton();
                    router.push('/') //跳转到首页
                } else {
                    ElMessage.error("用户名密码错误!");
                }
            });
        }
    });
};

存储token和id

//提交表单引用的函数
const submitForm = () => {
    //校验表单
    loginFormRef.value.validate((valid) => {
        if (valid) {
            axios.post("http://127.0.0.1:3000/login", loginForm).then((res) => {
                if (res.data.id) {
                    Cookie.set('id', res.data.id); //存储id
                    Cookie.set('token', res.data.token); //存储token
                    let buttonPinia = useButton();
                    buttonPinia.getButton();
                    router.push('/') //跳转到首页
                } else {
                    ElMessage.error("用户名密码错误!");
                }
            });
        }
    });
};

跳转到首页

//提交表单引用的函数
const submitForm = () => {
    //校验表单
    loginFormRef.value.validate((valid) => {
        if (valid) {
            axios.post("http://127.0.0.1:3000/login", loginForm).then((res) => {
                if (res.data.id) {
                    Cookie.set('id', res.data.id); //存储id
                    Cookie.set('token', res.data.token); //存储token
                    let buttonPinia = useButton();
                    buttonPinia.getButton();
                    router.push('/') //跳转到首页
                } else {
                    ElMessage.error("用户名密码错误!");
                }
            });
        }
    });
};

拦截跳转,触发action )

跳转拦截router.beforeEach()

(vux中action一般用来异步请求这里我们用pinia实现相应请求路由数据)

创建相关文件目录

/src/stores/routers.js

/src/router/index.js

/src/perssion.js

index.js

// router.js
import { createRouter, createWebHistory } from 'vue-router';
// 创建router实例
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/mainbox',name: 'mainbox',redirect:'/', component: ()=> import('../views/mainBox.vue')},
    // { path: '/',name: 'index',component: ()=> import('../views/mainBox.vue')},
    // { path: '/page4',name:'page4', component: () => import('../views/page4.vue')}, 
    { path: '/about', name: 'about', component: () => import('../views/about.vue')},
    { path: '/login',name:'login', component: () => import('../views/login.vue')}
  ],//不需要进行权限验证的路由
});
export default router;

perssion.js文件中的routerPinia.getRouter()触发了相应路由的异步请求

import router from './router/index'
import Cookies from 'js-cookie'
import { useRoutersStore } from './stores/routers'
let whiteList = ['/about', '/login']
router.beforeEach(async (to, from, next) => {
    let token = Cookies.get('token')
    if (token) {
        //如果已经登录还要去登录页面,则不允许跳转到登录页面
        if (to.path === '/login') {
            next('/')
        } else {
            let routerPinia = useRoutersStore();
            if (routerPinia.routerList.length > 0) { 
                //如果长度大于0代表着,已经请求过权限了
                // console.log(to.matched)
                if (to.matched.length > 0) {//路由中没有相应的路径的话to.matched.length 返回值是0
                    //能匹配上代表有这个页面
                    next()
                } else {
                    // //没有权限
                    alert('你没有权限访问')
                    next(from.path);
                }
            } else {
                //等于0 则代表没有, 就请求
                let _newArr = await routerPinia.getRouter();
                //添加动态路由
                // router.addRoute('mainbox',_newArr)
                _newArr.forEach((item) => {
                    // console.log(item)
                    router.addRoute('mainbox',item)
                })
                // console.log(router)
                //继续跳转
                next(to.path);
            }
        }
    } else {
        //如果在白名单,则直接访问
        if (whiteList.indexOf(to.path) != -1) {
            next();
        } else {
            //如果不在,代表需要权限,则打回登录
            next('/login')
        }
    }
})

pinia实现的routers.js(其实和vux类似)

// stores/counterStore.js
import { ref, } from 'vue'
import axios from 'axios'
import Cookies from 'js-cookie'
import { defineStore } from 'pinia'
import {parseRouter}  from '@/hooks/parseRouter.js'
export const useRoutersStore = defineStore('routers', () => {
    const routerList = ref([])
    function getRouter() {
        let id = Cookies.get('id')
        return new Promise((resolve, reject) => {
            //看看本地有没有存储当前用户的菜单信息
            let _local = JSON.parse(localStorage.getItem('routerList' + id));
            if (_local) {
                let _newArr = parseRouter(_local)
                //更新到state
                routerList.value = _newArr
                //给出去
                resolve(_newArr);
            } else {
                axios.get('http://127.0.0.1:3000/routes?id=' + id).then(res => {
                    //形成符合路由规则的新数组
                    let _newArr = parseRouter(res.data)
                    //更新到state
                    routerList.value = _newArr
                    //存到localstore中解决重复请求数据问题
                    localStorage.setItem('routerList' + id, JSON.stringify(res.data))
                    //给出去
                    resolve(_newArr);
                })
            }
        })
    }

    function resetRouter() {
        routerList.value = []
    }
    return { routerList, getRouter, resetRouter }
})

获取规则,并格式化

在pinia的routers.js文件中的自定义parseRouter()方法实现数据格式化 ,并且我这里将这个方法单独抽离了出来

let module = import.meta.glob('@/views/*.vue')
import { reactive } from 'vue';
//格式化路由方法 
export function parseRouter(menuList) {
    // 用于保存存在子路由的路由数据
    let routes = reactive([])
    let route = []
    menuList.forEach((item) => {
        let _newRouteRul = Object.assign({}, item);
        if (_newRouteRul.children && _newRouteRul.children.length > 0) {
            // 获取路由的基本格式
            route = obj(_newRouteRul)
            // 递归处理子路由数据,并返回,将其作为路由的 children 保存
            route.children = parseRouter(_newRouteRul.children)
            // 保存存在子路由的路由
            routes.push(route)
        } else {
            // 保存普通路由
            routes.push(obj(_newRouteRul))
        }
    })
    // 返回路由结果
    return routes
}

// 返回路由的基本格式
const obj = (item) => {
    let route = {
        // 路由的路径
        path: item.path,
        // 路由名
        name: item.name,
        // 路由所在组件
        // component: (resolve) => require([`@/layout/Index`], resolve),
        component: module[`/src/views/${item.component}.vue`],
        // meta: {
        //   id: item.id,
        //   icon: item.icon
        // },
        // 路由的子路由
        children: []
    }
    // 返回 route
    return route
}   

addRoute添加菜单

1440927bd1fc4ffe8a51b365cc737569.png

存储到state

cd72d7c44b7a4a05910a497334c06a65.png

存储菜单到本地

2ea775b5914847d98a8f98f6495c1e55.png实现效果

34ff0422348943c6acf5cd33e9400a40.png

代码地址:司徒飞/vue-permissions-node-servericon-default.png?t=N7T8https://gitee.com/situ_fei/vue-permissions-node-server.git


司徒飞/vue-permissionsicon-default.png?t=N7T8https://gitee.com/situ_fei/vue-permissions.git

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值