收款定制开发vue3 一个基于pinia简单易懂的系统权限管理实现方案,vue-router动态路由异步问题解决

文章目录

前情提要


收款定制开发作为项目经验稀少的vue收款定制开发开发者来说,收款定制开发在关键技术点上的经验不多,收款定制开发我希望通过我的思想和实践,收款定制开发把好的东西分享在这里,收款定制开发目的是进一步促进技术交流。收款定制开发项目即将完成,收款定制开发权限是最后的收尾工作,收款定制开发好的权限实现方案,收款定制开发可以让我们没有后顾之忧,收款定制开发也可以提升项目的运行速度。


应用场景

收款定制开发在开发之前,我粗略的浏览了一些权限实现方法,可以说智者见智吧,例如一种实现方案是在router的守卫里判断,我认为虽然实现了功能,但是增加了路由的功能压力。我们的需求是登录后即获知权限,根据权限提供功能;根据以上俩点需求我做出了如下计划:

  1. 找一个合适的入口,判断要提供的菜单以及用户拥有什么权限,不能影响页面加载
  2. 在对应的页面中拿到权限,提供需要的功能
  3. 必须整个系统都可以轻易拿到,且要避免安全问题

实战解析

大多的实现方案是添加路由,我也实践了一次,发现存在问题,于是我准备反其道而行,初始提供所有路由,根据权限移除不需要的路由,上面我分析到,我不想给导航守卫添加过多的压力,避免影响页面渲染,所以我利用了pinia状态库+router.removeRoute来实现路由的控制;

1、控制添加路由

首先我们应该准备基础的router配置,包括公共页面,异常页面等:

//router/index.jsimport { createRouter, createWebHistory } from 'vue-router'import { useUsersStore } from "@/store/user";import Cookies from "js-cookie";import { isUserTime } from "@/tool/unitl.js"const routes = [    {        path: '/',        name: 'Index',        component: () => import('@/view/Index.vue'),    },    {        path: '/login',        name: 'Login',        component: () => import('@/view/Login.vue')    },    {        path: '/business',        name: 'Business',        component: () => import('@/view/business/Index.vue')    },    {        path: '/business/project',        name: 'BusinessProject',        component: () => import('@/view/business/Project.vue')    },    {        path: '/commerce',        name: 'Commerce',        component: () => import('@/view/commerce/Index.vue')    },    {        path: '/commerce/project',        name: 'CommerceProject',        component: () => import('@/view/commerce/Project.vue')    },    {        path: '/consult',        name: 'Consult',        component: () => import('@/view/consult/Index.vue')    },    {        path: '/consult/project',        name: 'ConsultProject',        component: () => import('@/view/consult/Project.vue')    },    {        path: '/finance',        name: 'Finance',        component: () => import('@/view/finance/Index.vue'),        children: [            {                path: 'projects/project',                name: 'FinanceProjectsProject',                component: () => import('@/view/finance/projects/Project.vue')            },            {                path: 'employee/project',                name: 'FinanceEmployeeProject',                component: () => import('@/view/finance/employee/Project.vue')            },            {                path: 'account',                name: 'FinanceAccount',                component: () => import('@/view/finance/account/Index.vue')            },            {                path: 'authority/project',                name: 'FinanceAuthority',                component: () => import('@/view/finance/authority/Project.vue')            }        ]    },    {        path: '/chief',        name: 'Chief',        component: () => import('@/view/chief/Index.vue'),        children: [            {                path: 'first_examine/project',                name: 'firstExamineProject',                component: () => import('@/view/chief/first_examine/Project.vue')            },            {                path: 'second_examine/project',                name: 'secondExamineProject',                component: () => import('@/view/chief/second_examine/Project.vue')            }        ]    },    {        path: '/email',        name: 'Email',        component: () => import('@/view/email/Index.vue')    },    {        path: '/person',        name: 'Person',        component: () => import('@/view/person/Detail.vue')    },    {        path: '/:pathMatch(.*)*',        name: '404',        component: () => import('@C/tool/Page404.vue')    }]const router = createRouter({    history: createWebHistory(),  //history    routes})router.beforeEach(async (to, from) => {    //pinia仓库可以最早出现的地方,路由初始化完毕    const store = useUsersStore();    if (to.name == '404' && from.name == 'Login') {        //处理404问题,并且从login跳转任何404页面重置为Login页面        return { name: 'Login' };    } else if (store.isLogin) {         //不是404,登录状态在,正常跳转        return true;    } else if (isUserTime() && Cookies.get("loginPwd")) {         //刷新进入,判断是否过期了登录时间,Cookie中是否存在密码        //如果存在免登录,如果登录过程存在路由不存在的问题则404,否则正常跳转        let exist = await store.Login(to);        if (!exist) return { name: '404' };    }    else return { name: "Login" };});export default router
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

以上代码为router所有前期需要准备的代码,已经我处理路由异常情况的实现方案,核心是利用:
async await 俩个关键字实现,因为在登录后,我们配置移除菜单和权限是需要时间的,必须要严格控制异步,这里也顺便处理了路由输入回车跳转情况, 通过await store.Login(to);传入了to的参数来判断如何跳转,接下来参考下我的仓库实现过程;

//src/store/user.tsimport { defineStore } from 'pinia'import { apiGetUser } from '@/axios/user.js'import CryptoJS from 'crypto-js'import router from '@/router/index.js'// 第一个参数是应用程序中 store 的唯一 idexport const useUsersStore = defineStore('users', {    state: () => {        return {            isLogin: false,            phone: sessionStorage.UserN ? CryptoJS.AES.decrypt(                sessionStorage.getItem('UserN'),                "abc!"            ).toString(CryptoJS.enc.Utf8) : '',            pwd: "",            deptno: "",            idCard: "",            lockState: "",            birthday: "",            name: "",            sex: "男",            power: '',//权限:0-admin,1-部门总负责人,2-部门项目负责人,3-部门成员            showMenu: '',        }    },    actions: {        Login(to) {            let _that = this;            let MenuArray = ['Business', 'BusinessProject', 'Commerce', 'CommerceProject', 'Consult', 'ConsultProject', 'Finance', 'Chief'];            return new Promise((t, f) => {                _that.isLogin = true;                let data = new FormData();                data.append('loginAct', _that.phone)                apiGetUser(data).then(res => {                    let { deptno, idCard, lockState, name, sex, birthday } = res, showMenu = deptno.charAt(0);                    console.log(res);                    _that.$patch({                        deptno,                        idCard,                        lockState,                        name,                        sex,                        birthday,                        showMenu                    })                    if (showMenu == 'R') {                        t(true)                    } else {                        //过滤菜单项                        switch (showMenu) {                            case 'A': MenuArray = MenuArray.filter(item => {                                return item.indexOf('B') == -1                            }); break;                            case 'B': MenuArray = MenuArray.filter(item => {                                return item.indexOf('Com') == -1                            }); break;                            case 'C': MenuArray = MenuArray.filter(item => {                                return item.indexOf('Con') == -1                            }); break;                            case 'D': MenuArray = MenuArray.filter(item => {                                return item.indexOf('F') == -1                            }); break;                            case 'E': MenuArray = MenuArray.filter(item => {                                return item.indexOf('Ch') == -1                            }); break;                        }                        //循环移除                        let Length = MenuArray.length;                        MenuArray.forEach((item, index) => {                            if (index == Length - 1) {                                router.removeRoute(item);                                if(to.name && router.hasRoute(to.name)){                                    t(true)                                }else{                                    t(false)                                }                            } else {                                router.removeRoute(item)                            }                        })                    }                })            })        },    },})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

这里我直接展示了我的代码,如果你不熟悉pinia,要抓紧补了,在用户登录验证成功后,这里会调用user库的login函数,也会携带路由要to的参数,这里只需要明确几个点即可:

  1. MenuArray,将来用来循环removeRoute使用的数组,映射路由name
  2. 根据自己的配置规则,筛选 MenuArray
  3. 通过index == Length - 1 严格控制最后一次移除,并且判断to.name是否合法
  4. 理解await原理,await必须出现在async函数中,且 返回函数如果是Promise,则只接受resolved中的值;
  5. router, 熟悉vue3的同志知道 有一个useRouter 驱动函数,在我们的方案里不能那样使用,因为useRouter必须在setup函数中使用;

2、实践观察

我们的需求完美实现,接下来就是把不需要的菜单隐藏掉

3、控制功能

路由我们已经控制住了,隐藏菜单就很容易了,加载userStore 获取条件v-if即可,例如在header组件中:

//header.vueimport { useUsersStore } from "@/store/user.js"const user = useUsersStore();//html<el-menu-item index="/">首页</el-menu-item><el-menu-item v-if="'AR'.indexOf(user.showMenu) != -1" index="/business">业务部</el-menu-item><el-menu-item v-if="'BR'.indexOf(user.showMenu) != -1" index="/commerce">商务部</el-menu-item><el-menu-item v-if="'CR'.indexOf(user.showMenu) != -1" index="/consult">咨询部</el-menu-item><el-menu-item v-if="'DR'.indexOf(user.showMenu) != -1" index="/finance">财务部</el-menu-item><el-menu-item v-if="'ER'.indexOf(user.showMenu) != -1" index="/chief">总工办</el-menu-item>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11


这边根据自己团队的权限规范条件处理即可,其他的控制举一反三

4、解决异步路由问题

router.beforeEach(async (to, from) => {    //pinia仓库可以最早出现的地方,路由初始化完毕    const store = useUsersStore();    if (to.name == '404' && from.name == 'Login') {        //处理404问题,并且从login跳转任何404页面重置为Login页面        return { name: 'Login' };    } else if (store.isLogin) {         //不是404,登录状态在,正常跳转        return true;    } else if (isUserTime() && Cookies.get("loginPwd")) {         //刷新进入,判断是否过期了登录时间,Cookie中是否存在密码        //如果存在免登录,登录过程存在路由不存在问题则404,否则正常跳转        let exist = await store.Login(to);        if (!exist) return { name: '404' };    }    else return { name: "Login" };});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这里是我项目的路由守卫,它已经完成绝大部分功能,登录权限判断,404处理,刷新进入,状态持久化,异步操作在网络环境中是无法避免的,router官方示例也明确提醒了我们如何使用,这里我们是利用服务器数据判断要移除的菜单,且不说http响应异步,router.removeRoute()也只能是一次删除一个,所以在store.Login(to)我严格控制了逻辑,并且也解决了上述问题,使用我这样的全局守卫来配置必须要理清楚逻辑:依靠 async及await来实现,缺点是会有短暂的延迟,还不够流畅,但是笔者由于时间问题,不能再优化下去,希望大家吸取精华去掉糟粕。

至此我的分享结束,如果大家有更好的解决方案,希望可以进一步交流。


最后

📚
☃️ 个人简介:一个喜爱技术的人。
🌞 励志格言: 脚踏实地,虚心学习。
❗如果文章还可以,记得用你可爱的小手点赞👍关注✅,我会在第一时间回、回访,欢迎进一步交流。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发