前端实现菜单按钮级权限

核心思想就是通过登录请求此用户对应的权限菜单,然后跳转首页,触发全局前置导航守卫,在全局导航守卫中通过 addRoute 添加动态路由进去。addRoute有一个需要注意的地方,就是我们添加完动态路由后,地址栏上立即访问添加的动态路由,它不会跳转,需要我们手动触发下,push或者replace都可以进行触发。但是用在全局前置导航守卫中,写法又不太一样,可以参照官网说明

动态路由 | Vue Router (vuejs.org)

这是我自己练习的项目文件分布

我这里把主要文件的代码都贴出来

首先是 pinia 文件,这里面主要存储了 token 与 动态路由 数据。我做了一个持久化存储

stores 里面的 counter.ts

import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', () => {
  // token
  const token = ref('')
  // 动态路由
  const dynamicRoutes = ref([])

  // 设置token
  const setToken = (t: string) => { token.value = t }
  // 设置动态路由
  const setDynamicRoutes = (r: any) => { dynamicRoutes.value = r }

  // 清空token
  const clearToken = () => { token.value = '' }
  // 清空动态路由
  const clearDynamicRoutes = () => { dynamicRoutes.value = [] }

  return {
    token,
    dynamicRoutes,
    setToken,
    setDynamicRoutes,
    clearToken,
    clearDynamicRoutes
  }
}, {
  persist: {
    enabled: true // true 表示开启持久化保存
  }
})

路由信息  router 文件夹里面的 index.ts 

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
      // 重定向的首页
      redirect: '/test'
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('../views/Login.vue')
    }
  ]
})

export default router

登录页面:views 文件夹里面的 Login.vue

<template>
    <div>
        <el-button @click="submit">登录</el-button>
    </div>
</template>

<script setup lang="ts">
import { useUserStore } from '@/stores/counter'
import { useRouter } from 'vue-router'

const userStore = useUserStore()
const router = useRouter()

const submit = () => {
    // 模拟登录
    setTimeout(() => {
        // 存储token和动态路由
        userStore.setToken('Bearen Xxx')
        userStore.setDynamicRoutes([
            {
                path: '/about',
                name: 'about',
                children: [
                    { path: '/about/music', name: 'music', component: 'Music' },
                    { path: '/about/movie', name: 'movie', component: 'Movie' },
                    {
                        path: '/about/parent',
                        name: 'parent',
                        children: [
                            { path: '/about/parent/child', name: 'parent', component: 'Parents' }
                        ]
                    },
                ]
            },
            { path: '/test', name: 'test', component: 'Test' },
        ])
        // 跳转路由。触发全局前置导航守卫
        router.replace('/')
    })
}
</script>

点击登录页面的登录按钮后,会跳转到  '/'  ,路由变化了,就会触发全局前置导航守卫,全局前置导航守卫我写在了 mian.ts 文件中

自定义指令可以忽略,那是我做按钮级权限用的(可以看我上一篇文章)。

需要注意的地方就是 hasAddAliveRoutes 这个变量,记录是否已经添加过动态路由了,如果添加过了,就赋值为true。在去其他路由的时候,就不会重新添加动态路由了。还有一个作用就是,刷新的时候,hasAddAliveRoutes会重新变为false,会重新添加一下动态路由,防止刷新路由丢失

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import piniaPersist from 'pinia-plugin-persist'
import { useUserStore } from './stores/counter'

const app = createApp(App)

const pinia = createPinia()
app.use(pinia)
app.use(router)
app.use(ElementPlus)

pinia.use(piniaPersist)

// 处理动态路由,下面的全局前置导航守卫会用到
const getNewRoutes = (routes: any) => {
    let res = routes.map((i: any) => {
        return {
            ...i,
            // 动态添加component,有就添加,没有就不添加 (带有子级的路由是没有component的)
            ...(i.component && { component: () => import(`./views/${i.component}.vue`) }),
            // 再把内层的处理一下
            ...(i.children && { children: getNewRoutes(i.children) }),
        }
    })
    return res
}

// 是否添加过动态路由 这里的标识作用:刷新的时候,会变为false,然后就会重新添加动态路由,防止路由丢市的
let hasAddAliveRoutes = false
router.beforeEach((to, from, next) => {
    if (to.path === '/login') {
        next()
    } else {
        let token = useUserStore().token
        if (token) { // 存在token
            // 判断是否有动态路由添加了
            if (!hasAddAliveRoutes) { // 没有添加,则添加动态路由并进行触发
                // 对pinia中的动态路由进行处理,component字段只是一个文件名称,不是我们想要的动态引入,所以需要修改
                let arr = getNewRoutes(useUserStore().dynamicRoutes)
                console.log('处理之后的路由', arr)
                // 开始添加动态路由
                for (let i = 0; i < arr.length; i++) {
                    router.addRoute('home', arr[i])
                }
                // 修改添加动态路由的状态
                hasAddAliveRoutes = true
                // 触发添加的动态路由
                next(to.fullPath)
            } else { // 已经添加了,则直接通过
                next()
            }
        } else { // 不存在token
            next('/login')
        }
    }
})


// 假装此用户在tset页面只有 改 和 查 的按钮权限
let buttonAuth = [
    { path: '/test', btn: ['check', 'change'] }
]
// 自定义指令: 控制按钮级权限
app.directive('permission', {
    mounted(el, binding) {
        // console.log(el) // 元素
        // console.log(binding.value) // 值
        // console.log(binding.arg) // 路由

        // 遍历按钮数组,根绝当前的路由找到这一项的按钮权限
        let btnAuth = buttonAuth.find(item => item.path === binding.arg)
        if (btnAuth) { // 找到了
            // 不包含此按钮权限就移除按钮
            !btnAuth.btn.includes(binding.value) && el.parentNode.removeChild(el)
        }
    }
})

app.mount('#app')

然后就会跳往首页了,也就是 HomeView.vue 页面,这个文件里面用到了递归组件MenuTree.vue 

这个递归组件就是用来递归菜单的,多少级菜单都能进行展示

<template>
  <el-container class="layout-container-demo" style="height: 100%">
    <el-aside :width="aside">
      <el-scrollbar>
        <el-menu
          :router="true"
          :collapse="isCollapse"
          :mode="mode"
          :collapse-transition="false"
          :default-active="$router.currentRoute.value.path"
        >
          <MenuTree :routes="routes" :isCollapse="isCollapse"></MenuTree>
        </el-menu>
      </el-scrollbar>
    </el-aside>

    <el-container>
      <el-header style="text-align: right; font-size: 12px">
        <div class="toolbar">
          <el-button @click="collapse">折叠菜单</el-button>
          <el-button @click="changeMode">改变布局</el-button>
          <el-dropdown>
            <el-icon style="margin-right: 8px; margin-top: 1px">
              <setting />
            </el-icon>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>View</el-dropdown-item>
                <el-dropdown-item>Add</el-dropdown-item>
                <el-dropdown-item>Delete</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
          <span @click="reback">退出</span>
        </div>
      </el-header>

      <el-main>
        <el-scrollbar>
          <router-view></router-view>
        </el-scrollbar>
      </el-main>
    </el-container>
  </el-container>
</template>

<script lang="ts" setup>
import { Setting } from '@element-plus/icons-vue'
import MenuTree from './MenuTree.vue'
import { useUserStore } from '@/stores/counter'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const userStore = useUserStore()
const router = useRouter()

// 路由
const routes = ref(userStore.dynamicRoutes)
// 是否折叠
const isCollapse = ref(false)
// 宽度
const aside = ref('200px')
// 菜单展示方式
const mode = ref('vertical')

// 点击折叠菜单
const collapse = () => {
  isCollapse.value = !isCollapse.value
  aside.value = isCollapse.value ? '60px' : '200px'
}
// 点击改变布局
const changeMode = () => {
  mode.value = mode.value === 'vertical' ? 'horizontal' : 'vertical'
}
// 点击退出
const reback = () => {
  userStore.clearToken()
  userStore.clearDynamicRoutes()
  router.replace('/login')
}
</script>

<style scoped>
.layout-container-demo .el-header {
  position: relative;
  background-color: var(--el-color-primary-light-7);
  color: var(--el-text-color-primary);
}

.layout-container-demo .el-aside {
  color: var(--el-text-color-primary);
  background: var(--el-color-primary-light-8);
  /* 新加的过度效果 */
  transition: width 0.15s;
  -webkit-transition: width 0.15s;
  -moz-transition: width 0.15s;
  -webkit-transition: width 0.15s;
  -o-transition: width 0.15s;
}

.layout-container-demo .el-menu {
  border-right: none;
}

.layout-container-demo .el-main {
  padding: 0;
}

.layout-container-demo .toolbar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  right: 20px;
}
</style>

递归组件MenuTree.vue

<template>
  <div v-for="item in routes" :key="item.path">
    <!-- 一级菜单 -->
    <el-menu-item :index="item.path" v-if="!item.children">
      <el-icon>
        <message />
      </el-icon>
      <span v-show="!isCollapse">
        {{ item.path }}
      </span>
    </el-menu-item>

    <!-- 多级菜单 -->
    <el-sub-menu :index="item.path" v-else>
      <template #title>
        <!-- <el-icon> -->
        <el-icon class="more-menu-icon-hover">
          <message />
        </el-icon>
        <span v-show="!isCollapse">
          {{ item.path }}
        </span>
      </template>
      <MenuTree :routes="item.children"></MenuTree>
    </el-sub-menu>
  </div>
</template>

<script setup lang="ts">
import { Message } from '@element-plus/icons-vue'

defineProps({
  routes: {
    type: Array,
    default: () => [],
  },
  isCollapse: Boolean,
})
</script>

<style scoped>
/* 新增的样式,解决箭头问题 */
.more-menu-icon-hover {
  z-index: 99;
  background-color: #fff;
  transition: all 0.3s;
}

:deep(.el-sub-menu__title:hover) {
  .more-menu-icon-hover {
    background-color: #ecf5ff;
  }
}
</style>

但是路由规则数组中,我其实做了重定向。也就是路由匹配到 '/' 的时候,会重定向到 /test ,也就是Test.vue页面。首页HomeView.vue文件中我指定的有二级路由出口,所以Test.vue页面的内容会展示在HomeView.vue页面的路由出口处

<template>
    <div>
        <el-button v-permission:[currentRoute]="'add'">增加</el-button>
        <el-button v-permission:[currentRoute]="'delete'">删除</el-button>
        <el-button v-permission:[currentRoute]="'change'">修改</el-button>
        <el-button v-permission:[currentRoute]="'check'">查看</el-button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
// 获取当前的路由
const currentRoute = ref(router.currentRoute.value.path)
</script>

好了,到这里,前端路由其实就已经做好了。实现的是菜单级别和按钮级别的权限

下面的百度网盘地址,有需要的可以自提:安装依赖后,直接 npm run dev 即可启动

链接:https://pan.baidu.com/s/1qYq8TzsroanggPfhVQEPoQ?pwd=x89u 
提取码:x89u

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

django建站过程(3)定义模型与管理页

定义模型与管理页 定义模型[models.py]迁移模型向管理注册模型[admin.py]注册模型使用Admin.site.register(模型名)修改Django后台管理的名称定义管理列表页面应用名称修改管理列表添加查询功能 django shell交互式shell会话 认证和授权 定义模型[models.py] 模仿博客形式&…

鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统项目背景

鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管…

Dynamics 365 重写自带按钮

必备工具&#xff1a;Ribbon Workbench 步骤&#xff1a; 1、查看默认按钮使用的方法名称 右键按钮选择自定义命令(Customise Command)&#xff0c;然后查看command使用的命令 2、在前台chrome浏览器中搜索对应的命令&#xff0c;查看命令细节 3、基于命令细节新建command&am…

Python第三方库 - Flash(python web框架)

1 Flask 1.1 认识Flask Web Application Framework&#xff08; Web 应用程序框架&#xff09;或简单的 Web Framework&#xff08; Web 框架&#xff09;表示一个库和模块的集合&#xff0c;使 Web 应用程序开发人员能够编写应用程序&#xff0c;而不必担心协议&#xff0c;线…

分布式限流:Redis

目录 1:如何实现分布式限流 2:限流的几种类别 2.1:固定窗口限流 2.2:滑动窗口限流 2.3:漏桶限流 2.4:令牌桶限流 3:实现分布式限流:Redis 3.1:引入Redisson的依赖包 3.2:初始化Redisson 3.3:创建Redisson的限流类 1:如何实现分布式限流 1:把统计用户的使用频率等这些…

66 跳跃游戏

跳跃游戏 题解1 贪心题解2 DP&#xff08;超时, 但思路应该对&#xff09; 给你一个非负整数数组 nums&#xff0c;你最初位于数组的 第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 …

ilr normalize isometric log-ratio transformation

visium_heart/st_snRNAseq/05_colocalization/create_niches_ct.R at 5b30c7e497e06688a8448afd8d069d2fa70ebcd2 saezlab/visium_heart (github.com) 更多内容&#xff0c;关注微信&#xff1a;生信小博士 The ILR (Isometric Log-Ratio) transformation is used in the anal…

onebound电商API接口商品数据采集平台:让数据成为生产力!

随着数字化商业时代的到来&#xff0c;API接口已成为电商资源连接利器&#xff0c;也是全球传统互联网企业转型的基础。 2021年 Google Cloud 研究显示&#xff0c;全球互联网企业近3/4的企业持续投入数字化转型&#xff0c;2/3的企业在持续增加投入&#xff0c;从这组数据可以…

2023高频前端面试题-vue

1. 什么是 M V VM Model-View-ViewModel 模式 Model 层: 数据模型层 通过 Ajax、fetch 等 API 完成客户端和服务端业务模型的同步。 View 层: 视图层 作为视图模板存在&#xff0c;其实 View 就是⼀个动态模板。 ViewModel 层: 视图模型层 负责暴露数据给 View 层&…

SpringBoot+SpringMVC+MybatisPlus

文章目录 SpringBootSpringMVCMybatisPlus怎样在SpringBoot中引入SpringMVC?首先看下引入的依赖创建数据库表创建DO类创建MyBatisPlus动态代理接口创建controller控制器接收http请求创建SpringBoot配置文件application.yml最后创建启动类 SpringBootSpringMVCMybatisPlus 怎样…

SYS/BIOS 开发教程: 创建自定义平台

目录 SYS/BIOS 开发教程: 创建自定义平台创建自定义平台新建工程并指定自定义平台修改现有工程使用自定义平台 参考: TI SYS/BIOS v6.35 Real-time Operating System User’s Guide 6.2节 本示例基于 EVMC6678L 开发板, 创建自定义平台, 并将代码段的位置指定到C6678器件内部的…

【网络安全 --- 任意文件下载漏洞(1)】任意文件下载漏洞

一&#xff0c;环境&#xff0c;工具准备 1-1 VMVare 16 虚拟机及下载安装&#xff08;资源&#xff09; 请参考以下博客安装&#xff08;特详细&#xff09;&#xff1a;【网络安全 --- 工具安装】VMware 16.0 详细安装过程&#xff08;提供资源&#xff09;-CSDN博客【网络安…

前后端交互系统:在Node.js中运行JavaScript

在Node.js中运行JavaScript&#xff0c;您需要编写适用于服务器端的代码&#xff0c;而不是浏览器端的代码。以下是一些示例代码&#xff0c;用于在Node.js中创建一个简单的HTTP服务器并在浏览器中访问它&#xff1a; // 引入Node.js内置的http模块 const http require(http);…

使用Vscode创建一个C_Hello程序

Vscode用来学习C语言语法确实很方便。问题是安装好了&#xff0c;不会用&#xff0c;或编译失败&#xff0c;也是常有的事情&#xff0c;其中一个原因就是不会创建工作区。下面介绍使用Vscode创建一个C语言工作区。有时候看着很简单&#xff0c;时间久了&#xff0c;我竟然忘记…

对企业数字化转型有哪些建议?

对于希望在当今快速变化的商业环境中保持竞争力和相关性的企业来说&#xff0c;数字化转型是一个关键过程。以下是成功数字化转型的一些建议&#xff1a; 1.定义明确的目标&#xff1a;首先为数字化转型定义具体、可衡量且现实的目标。这些目标应与您的业务战略保持一致并解决…

spark获取hadoop服务token

spark 作业一直卡在accepted 问题现象问题排查1.查看yarn app日志2.问题分析与原因 问题现象 通过yarn-cluster模式提交spark作业&#xff0c;客户端日志一直卡在submit app&#xff0c;没有运行 问题排查 1.查看yarn app日志 appid已生成&#xff0c;通过yarn查看app状态为…

一起学数据结构(12)——归并排序的实现

1. 归并排序原理&#xff1a; 归并排序的大概原理如下图所示&#xff1a; 从图中可以看出&#xff0c;归并排序的整体思路就是把已给数组不断分成左右两个区间&#xff0c;当这个区间中的数据数量到达一定数值时&#xff0c;便返回去进行排序&#xff0c;整体的结构类似二叉树…

如何查询MAC 的本地IP出口

Terminnal 内执行 curl ip.gs

UE5场景逐渐变亮问题

1、显示 -- 关闭眼部适应 2、项目设置 -- 关闭自动曝光 参考&#xff1a; 虚幻5/UE5 场景亮度逐渐变亮完美解决方法 - 哔哩哔哩

记一次fineBI的增量删除更新BUG

官方文档链接是https://help.fanruan.com/finebi/doc-view-1663.html 按照官方文档&#xff0c;增量删除不能使用select * &#xff0c;且需要指定分区建 但实际指定分区键有时候也会报错&#xff0c;因为表设置的字段有时候会比数据源少&#xff0c;此时会报错&#xff0c;提…