vue3 + vite3 addRoute 实现权限管理系统

news2025/1/5 14:53:07

vue3 + vite3 addRoute 实现权限控制

  • 1、前言
  • 2、静态路由
  • 3、动态路由
  • 4、在组建中使用路由
  • 5、注意事项

1、前言

在权限系统开发中,根据后端返回的菜单列表动态添加路由是非常常见的需求,它可以实现根据用户权限动态加载可访问的页面。本篇文章我们将重点介绍动态添加路由的全过程。

2、静态路由

静态路由,也叫常量路由,即所有角色都可以访问到的路由界面。如: login404等。

export const constantRoute = [
	{
	    path: '/login',
	    component: () => import('@/views/login/index.vue'),
	    name: 'Login',
	    meta: {
	        title: '登录', //菜单标题
	        hidden: true, //代表路由标题在菜单中是否隐藏  true:隐藏 false:不隐藏
	        icon: 'Promotion',
	    },
	},
	{
	    path: '/',
	    component: () => import('@/layout/index.vue'),
	    name: '/',
	    meta: {
	        title: '',
	        hidden: false,
	
	    },
	    redirect: '/home',
	    children: [{
	        path: '/home',
	        component: () => import('@/views/home/index.vue'),
	        meta: {
	            title: '项目总览',
	            hidden: false,
	            icon: 'HomeFilled',
	        },
	    },],
	},
	{
	    path: '/user',
	    component: () => import('@/views/user/index.vue'),
	    name: 'User',
	    meta: {
	        title: '个人中心',
	        hidden: true,
	    },
	},
	{
	    path: '/404',
	    component: () => import('@/views/404/index.vue'),
	    name: '404',
	    meta: {
	        title: '找不到数据',
	        hidden: true,
	    },
	},
]

3、动态路由

即不同角色所拥有的权限路由,一般登录成功后,向后端发送请求,由服务器返回对应的权限,然后进行筛选过滤。

export const asyncRoute = [
    {
        path: '/management-project',
        component: () => import('@/layout/index.vue'),
        name: 'Management-project',
        meta: {
            title: '',
            icon: 'Grid'
        },
        redirect: '/management-project',
        children: [{
            path: '/management-project',
            component: () => import('@/views/project/index.vue'),
            name: 'Management-project',
            meta: {
                title: '项目管理',
                icon: 'Grid'
            },
        },],
    },

    {
        path: '/measurement-management',
        component: () => import('@/layout/index.vue'),
        name: 'Measurement-management',
        meta: {
            title: '测算管理',
            icon: 'Document'
        },
        redirect: '/measurement-management/common',
        children: [
            {
                path: '/measurement-management/common',
                component: () => import('@/views/measurement/common.vue'),
                name: 'Common',
                meta: {
                    title: '通用测算',
                    icon: 'Reading'
                },
            },
            {
                path: '/measurement-management/project',
                component: () => import('@/views/measurement/project.vue'),
                name: 'Project',
                meta: {
                    title: '项目测算',
                    icon: 'Folder'
                },
            },
        ]
    },
    {
        path: '/collection-management',
        component: () => import('@/layout/index.vue'),
        name: 'Collection-management',
        meta: {
            title: '收资管理',
            icon: 'Management'
        },
        redirect: '/collection-management/early-stage',
        children: [{
            path: '/collection-management/early-stage',
            component: () => import('@/views/collection-management/earlyStage.vue'),
            name: 'Early-stage',
            meta: {
                title: '前期收资',
                icon: 'List'
            },
        },
        {
            path: '/collection-management/scene',
            component: () => import('@/views/collection-management/scene.vue'),
            name: 'Scene',
            meta: {
                title: '现场踏勘',
                icon: 'View'
            },
        },
        {
            path: '/collection-management/later-stage',
            component: () => import('@/views/collection-management/laterStage.vue'),
            name: 'Later-stage',
            meta: {
                title: '后期收资',
                icon: 'List'
            },
        },
        ]
    },

    {
        path: '/audit-project',
        component: () => import('@/layout/index.vue'),
        name: 'Audit-project',
        meta: {
            title: '',
            icon: 'Checked'
        },
        redirect: '/audit-project',
        children: [{
            path: '/audit-project',
            component: () => import('@/views/audit/index.vue'),
            name: 'Audit-project',
            meta: {
                title: '项目审核',
                icon: 'Checked'
            },
        },],
    },
    {
        path: '/audit-project/profile',
        component: () => import('@/layout/index.vue'),
        name: 'Profile',
        meta: {
            title: '',
            hidden: false,
        },
        redirect: '/audit-project/profile',
        children: [{
            path: '/audit-project/profile',
            component: () => import('@/views/audit/profile/index.vue'),
            name: 'Profile',
            meta: {
                title: '投资评审报告',
                hidden: true,
                icon: 'Notebook'
            },
        },],
    },
]

//任意路由
export const anyRoute = {
    //任意路由
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    name: 'Any',
    meta: {
        title: '任意路由',
        hidden: true,
        icon: 'DataLine',
    },
}

用户登录成功之后,后端会根据其角色返回对应的路由信息,如图所示:
在这里插入图片描述
获取到后端返回的的路由信息之后,我们可以通过pinia状态管理工具进行管理,在store/mudules.user.js中进行定义。

// 管理用户数据
import {
  defineStore
} from 'pinia'

import {
  loginAPI,
  userInfoAPI,
  logoutAPI,
  roleDetail
} from '@/api/user'
import {
  SET_TOKEN,
  GET_TOKEN,
  REMOVE_TOKEN
} from '@/utils/token'
//引入路由(常量路由)
import {
  constantRoute,
  asyncRoute,
  anyRoute
} from '@/router/routes'
//引入深拷贝方法
import cloneDeep from 'lodash/cloneDeep'
import router from '@/router'
//用于过滤当前用户需要展示的异步路由
function filterAsyncRoute (asyncRoute, routes) {
  return asyncRoute.filter((item) => {
    if (routes.includes(item.name)) {
      if (item.children && item.children.length > 0) {
        item.children = filterAsyncRoute(item.children, routes)
      }
      return true
    }
  })
}

export const useUserStore = defineStore('userStore', {
  // 1.定义管理用户数据的state
  state: () => {
    return {
      token: GET_TOKEN(), // 用户唯一标识token
      menuRoutes: constantRoute, // 仓库存储生成菜单路由
      username: '', // 用户名
    }
  },
  //异步|逻辑的地方
  actions: {
    // 用户登录的方法
    async userLogin (data) {
      const res = await loginAPI(data)
      console.log(res)
      if (res.code === 200) {
        // pinia仓库存储token
        this.token = res.result.token
        // 本地持久化存储token
        SET_TOKEN(res.result.token)
        // 保证当前async函数返回是一个成功的promise
        return 'ok'
      } else {
        return Promise.reject(new Error(res.result.message))
      }
    },
    // 2.定义获取接口数据的action函数
    async userInfo () {
      const res = await userInfoAPI()
      console.log(res)
      //如果获取用户信息成功,存储一下用户信息 
      if (res.code == 200) {
        this.username = res.result.realname
        // 获取用户角色
        const roleData = await roleDetail({ roleCode: res.result.roleCode })
        console.log(roleData)
        if (roleData.code === 200) {
          //计算当前用户需要展示的异步路由
          const userAsyncRoute = filterAsyncRoute(
            cloneDeep(asyncRoute),
            roleData.result.powerCodes,
          )
          //菜单需要的数据整理完毕
          this.menuRoutes = [...constantRoute, ...userAsyncRoute, anyRoute]
          console.log(this.menuRoutes)
          console.log(userAsyncRoute)
          console.log(anyRoute);
          //目前路由器管理的只有常量路由:用户计算完毕异步路由、任意路由动态追加
          ;[...userAsyncRoute, anyRoute].forEach((route) => {
            router.addRoute(route)
          })
        }
        return 'ok'
      } else {
        return Promise.reject(new Error(result.message))
      }
    },
    //退出登录
    async userLogout () {
      //退出登录请求
      const res = await logoutAPI()
      console.log(res)
      if (res.code == 200) {
        this.token = ''
        this.username = ''
        REMOVE_TOKEN()
        localStorage.removeItem('username')
        return 'ok'
      } else {
        return Promise.reject(new Error(res.msg))
      }
    },
  }
})

4、在组建中使用路由

layout/index.vue文件

<template>
  <div class="layout-container">
    <!-- 左侧菜单 -->
    <div class="layout-slider">
      <Logo></Logo>
      <!-- 展示菜单 -->
      <!-- 滚动组件 -->
      <el-scrollbar class="scrollbar">
        <!-- 菜单组件 -->
        <el-menu :collapse="layoutSettingStore.fold ? true : false" :default-active="$route.path"
          background-color="#001529" active-text-color="#409EFF" text-color="white">
          <!-- 根据路由动态生成菜单 -->
          <Menu :menuList="userStore.menuRoutes"></Menu>
        </el-menu>
      </el-scrollbar>
    </div>
    <!-- 顶部导航 -->
    <div class="layout-tabbar" :class="{ fold: layoutSettingStore.fold ? true : false }">
      <Tabbar></Tabbar>
    </div>
    <!-- 内容展示区域 -->
    <div class="layout-main" :class="{ fold: layoutSettingStore.fold ? true : false }">
      <Main></Main>
    </div>
  </div>
</template>

<script  setup>
import { useRouter } from 'vue-router'
//引入左侧菜单logo子组件
import Logo from './logo/index.vue'
//引入菜单组件
import Menu from './menu/index.vue'
// 引入顶部导航
import Tabbar from './tabbar/index.vue'
//右侧内容展示区域
import Main from './main/index.vue'
// 获取用户相关的小仓库
import { useUserStore } from '@/store/modules/user'
import { useLayoutSettingStore } from '@/store/modules/setting'
const userStore = useUserStore()

const layoutSettingStore = useLayoutSettingStore()
// 获取路由对象
const $route = useRouter()
</script>

<script >
export default {
  name: 'Layout',
}
</script>
<style lang="scss" scoped>
.layout-container {
  width: 100%;
  height: 100vh;

  .layout-slider {
    color: white;
    width: $base-menu-width;
    height: 100vh;
    background: $base-menu-background;
    transition: all 0.3s;

    .scrollbar {
      width: 100%;
      height: calc(100vh - $base-menu-logo-height);

      .el-menu {
        border-right: none;
      }
    }
  }

  .layout-tabbar {
    position: fixed;
    top: 0;
    left: $base-menu-width;
    width: calc(100% - 260px);
    height: $base-tabbar-height;
    transition: all 0.3s;

    &.fold {
      width: calc(100vw - 50px);
      left: $base-menu-min-width;
    }
  }

  .layout-main {
    position: absolute;
    top: $base-tabbar-height;
    left: $base-menu-width;
    width: calc(100% - 260px);
    height: calc(100vh - 50px);
    background: #eceaec;
    padding: 10px;
    overflow: auto;
    transition: all 0.3s;

    &.fold {
      width: calc(100vw - 50px);
      left: $base-menu-min-width;
    }
  }
}
</style>

layout/menu/index.vue文件

<template>
  <template v-for="item in menuList" :key="item.path">
    <!-- 没有子路由 -->
    <template v-if="!item.children">
      <el-menu-item
        :index="item.path"
        v-if="!item.meta.hidden"
        @click="goRoute"
      >
        <el-icon>
          <component :is="item.meta.icon"></component>
        </el-icon>
        <template #title>
          <span>{{ item.meta.title }}</span>
        </template>
      </el-menu-item>
    </template>
    <!-- 有且只有一个子路由 -->
    <template v-if="item.children && item.children.length == 1">
      <el-menu-item
        :index="item.children[0].path"
        v-if="!item.children[0].meta.hidden"
        @click="goRoute"
      >
        <el-icon>
          <component :is="item.children[0].meta.icon"></component>
        </el-icon>
        <template #title>
          <span>{{ item.children[0].meta.title }}</span>
        </template>
      </el-menu-item>
    </template>
    <!-- 有大于一个子路由 -->
    <el-sub-menu
      :index="item.path"
      v-if="item.children && item.children.length > 1"
    >
      <template #title>
        <el-icon>
          <component :is="item.meta.icon"></component>
        </el-icon>
        <span>{{ item.meta.title }}</span>
      </template>
      <Menu :menuList="item.children"></Menu>
    </el-sub-menu>
  </template>
</template>

<script  setup>
import { useRouter } from 'vue-router'
// 获取路由器对象
const router = useRouter()
// 获取父组件传递的路由
defineProps(['menuList'])

// 点击菜单的回调函数
const goRoute = (vc) => {
  router.push(vc.index)
}
</script>
<script >
export default {
  name: 'Menu',
}
</script>
<style lang="scss" scoped></style>

5、注意事项

由于pinia中的数据是非持久性缓存的,所以一刷新数据就会丢失。
解决方案:使用pinia的持久性插件或者路由鉴权的同时,在路由前置导航守卫,每次跳转的时候,判断pinia中是否存储了用户信息,如果没有,重新调用getUserInfo方法,获取用户信息。

首先在根目录下定义一个permission.js文件:

//路由鉴权:鉴权,项目当中路由能不能被的权限的设置(某一个路由什么条件下可以访问、什么条件下不可以访问)
import router from '@/router'
import setting from './setting'
//@ts-ignore
import nprogress from 'nprogress'
//引入进度条样式
import 'nprogress/nprogress.css'
nprogress.configure({
    showSpinner: false
})
//获取用户相关的小仓库内部token数据,去判断用户是否登录成功
import {
    useUserStore
} from './store/modules/user'
import pinia from './store'
const userStore = useUserStore(pinia)
//全局守卫:项目当中任意路由切换都会触发的钩子
//全局前置守卫
router.beforeEach(async (to, from, next) => {
    document.title = `${setting.title} - ${to.meta.title}`
    //to:你将要访问那个路由
    //from:你从来个路由而来
    //next:路由的放行函数
    nprogress.start()
    //获取token,去判断用户登录、还是未登录
    // const token = localStorage.getItem("TOKEN")
    const token = userStore.token
    console.log(token)
    //获取用户名字
    const username = userStore.username
    console.log(username)
    //用户登录判断
    if (token) {
        //登录成功,访问login,不能访问,指向首页
        if (to.path == '/login') {
            next({
                path: '/'
            })
        } else {
            //登录成功访问其余六个路由(登录排除)
            //有用户信息
            if (username) {
                //放行
                next()
            } else {
                //如果没有用户信息,在守卫这里发请求获取到了用户信息再放行
                try {
                    //获取用户信息
                    await userStore.userInfo()
                    //放行
                    //万一:刷新的时候是异步路由,有可能获取到用户信息、异步路由还没有加载完毕,出现空白的效果
                    next({ ...to })
                } catch (error) {
                    //token过期:获取不到用户信息了
                    //用户手动修改本地存储token
                    //退出登录->用户相关的数据清空
                    await userStore.userLogout()
                    next({
                        path: '/login',
                    })
                }
            }
        }
    } else {
        //用户未登录判断
        if (to.path == '/login') {
            next()
        } else {
            next({
                path: '/login',
            })
        }
    }
})
//全局后置守卫
router.afterEach((to, from) => {
    nprogress.done()
})

在main.js中引入:

//引入路由鉴权文件
import './permission'

BUG:如果我们在动态路由页面进行刷新,会导致白屏
原因:刷新页面的时候,触发了路由前置导航守卫,获取用户信息,如果获取到了,就放行。但是放行的时候,动态路由还没有加载完成! 得确保获取完用户信息且全部路由组件渲染完毕
解决办法:next({...to})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1041834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第二届全国高校计算机技能竞赛——Java赛道

第二届全国高校计算机技能竞赛——Java赛道 小赛跳高 签到题 import java.util.*; public class Main{public static void main(String []args) {Scanner sc new Scanner(System.in);double n sc.nextDouble();for(int i 0; i < 4; i) {n n * 0.9;}System.out.printf(&…

探索公共厕所的数字化治理,智慧公厕完善公共厕所智能化的治理体系

随着城市化进程的不断发展&#xff0c;公共厕所治理成为一个不容忽视的问题。如何通过数字化手段来提升公共厕所管理水平&#xff0c;成为了一个备受关注的话题。本文将以智慧公厕领先厂家广州中期科技有限公司&#xff0c;大量精品案例项目实景实图&#xff0c;探讨公共厕所数…

品牌线上假货怎么治理

随着品牌的发展&#xff0c;母婴、家电、百货等行业&#xff0c;链接量暴增&#xff0c;销售店铺也较多&#xff0c;线上仅通过图片销售的形式&#xff0c;也导致了假货链接地滋生&#xff0c;假货分两种情况&#xff0c;一种是只销售假货的店铺&#xff0c;一种是真假混卖的店…

用numpy生成18种特殊数组

文章目录 单值数组特殊矩阵范德蒙德矩阵数值范围坐标网格绘图代码 所有创建数组的函数中&#xff0c;都有一个可选参数dtype&#xff0c;表示创建的数组的数据类型。 指定维度empty, eye, identity, ones, zeros, full模仿维度empty_like, ones_like, zeros_like, full_like特…

【Linux】C语言实现对文件的加密算法

异或加密 解密方式是进行第二次加密后自动解密 #define BUF_SIZE (16384) //16k /************************************************************** 功能描述: 加密实现 输入参数: --------------------------------------------------------------- 修改作者: 修改日期…

【小尘送书-第五期】《巧用ChatGPT快速提高职场晋升力》用ChatGPT快速提升职场能力,全面促进自身职业发展

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

qq录屏快捷键大全,玩转录制就这么简单(干货)

“qq有录屏快捷键吗&#xff1f;有点好奇&#xff0c;现在用qq录制屏幕&#xff0c;总是得去点击屏幕录制才可以出来&#xff0c;太麻烦了&#xff0c;如果可以通过快捷键的方式打开&#xff0c;会轻松许多&#xff0c;想问问大家&#xff0c;知道qq录屏快捷键是多少吗&#xf…

#你我都是国家队#,与泸州老窖一起为中国荣耀干杯

执笔 | 姜 姜 编辑 | 古利特 代表亚洲最高水平的体育盛会已经开幕两天&#xff0c;国家队运动员们在赛场上挥洒汗水&#xff0c;国人的激情也随之升温。 为迎接这场体育盛会&#xff0c;9月13日&#xff0c;TEAM CHINA中国国家队官方微博携手泸州老窖发布了一条态度短片&am…

R语言用标准最小二乘OLS,广义相加模型GAM ,样条函数进行逻辑回归LOGISTIC分类...

原文链接&#xff1a;http://tecdat.cn/?p21379 本文我们对逻辑回归和样条曲线进行介绍&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 logistic回归基于以下假设&#xff1a;给定协变量x&#xff0c;Y具有伯努利分布&#xff0c; 目的是估计参数β。 回想一…

如何在Python中实现高效的数据处理与分析

在当今信息爆炸的时代&#xff0c;我们面对的数据量越来越大&#xff0c;如何高效地处理和分析数据成为了一种迫切的需求。Python作为一种强大的编程语言&#xff0c;提供了丰富的数据处理和分析库&#xff0c;帮助我们轻松应对这个挑战。本文将为您介绍如何在Python中实现高效…

【深度学习实验】卷积神经网络(二):自定义简单的二维卷积神经网络

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 二维互相关运算&#xff08;corr2d&#xff09; 2. 二维卷积层类&#xff08;Conv2D&#xff09; a. __init__&#xff08;初始化&#xff09; b. forward(前向传…

Vue2 常用用法

Vue2 常用用法 Vue 动画1. 进入、离开的过渡2. 列表的过渡3. 状态的过渡 Vue 透传Attrbute、插槽1.透传Attrbute2. 插槽 CSS布局原则flex 布局常见的问题&#xff1a;当子元素内容超出父元素时&#xff0c;不出现滚动条的问题。父元素flex:1且内容超出后的最佳解决方案&#xf…

新版首途影视视频网站源码/22套带后台版全开源+无加密源码(全新二开完整版)

源码简介&#xff1a; 首途影视是一个非常受欢迎的视频网站&#xff0c;提供各种电影、电视剧、综艺节目等内容。它是一个基于Web的视频流媒体平台&#xff0c;你可以随时随地在手机上或电脑上在线观看自己喜欢的影视作品。 新版首途影视视频网站源码/22套带后台版全开源无加…

U盘恢复怎么做?4个方法,拯救你的u盘数据!

“我的u盘对我来说意义重大&#xff0c;里面保存着很重要的照片和视频。但是不知道是不是太久没使用&#xff0c;现在将u盘插入电脑后&#xff0c;有些数据好像丢失了&#xff0c;完全没办法查看了。怎么解决呢&#xff1f;” 随着u盘的广泛使用&#xff0c;数据丢失问题也更为…

7.16 SpringBoot项目实战 【学生入驻】(下):正确理解 编程式事务和声明式事务

文章目录 前言一、service层1. 提交学生信息2. 申请借阅资格3. 重新提交4. 事务 二、web层 StudentController三、测试最后 前言 通过上文&#xff0c;我们实现了【学生入驻】的第一个API&#xff1a;查询学生信息&#xff0c;接下来的流程通常如下图&#xff1a;如果学生未入…

Python第二次作业(2)【控制台界面】

要求&#xff1a;使用Python输出五个控制台界面 第一张&#xff1a; 代码如下&#xff1a; print(" 英雄联盟商城登录界面 ") print("~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*") print(" 1.用户登录 &q…

STL常用遍历、查找算法

目录 算法概述 常用遍历算法for_each 常用遍历算法transform 常用查找算法find 常用查找算法find_if 常用查找算法adjacent_find 常用查找算法binary_search 常用查找算法count 常用查找算法count_if 算法概述 算法主要是由头文件<algorithm><functional>…

Linux系统编程-文件

目录 1、系统编程介绍以及文件基本操作文件编程系统调用文件基本读写练习 2、文件描述符以及大文件拷贝文件描述符大文件拷贝对比试验 3、文件实战练习 1、系统编程介绍以及文件基本操作 系统编程是基于Linux封装好的一些函数&#xff0c;进行开发。 Linux文件信息属性在indo…

面试题:集群高并发环境下如何保证分布式唯一全局ID生成?

文章目录 前言问题为什么需要分布式全局唯一ID以及分布式ID的业务需求ID生成规则部分硬性要求ID号生成系统的可用性要求 一般通用解决方案UUID数据库自增主键 集群分布式集群基于Redis生成全局ID策略单机版集群分布式 雪花算法什么是雪花算法结构实现SpringBoot整合雪花算法 前…

【Pm4py第七讲】关于visualization

本节用于介绍pm4py中的可视化函数&#xff0c;包括可视化bpmn、petri、性能图谱、变迁系统等。 1.函数概述 本次主要介绍Pm4py中一些常见的可视化函数&#xff0c;总览如下表&#xff1a; 函数名说明view_alignments(log, aligned_traces[, format])可视化对齐方法 view_bpmn(…