前端实现菜单按钮级权限

news2024/9/24 19:22:40

核心思想就是通过登录请求此用户对应的权限菜单,然后跳转首页,触发全局前置导航守卫,在全局导航守卫中通过 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

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

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

相关文章

Linux PAGE_ALIGN 宏定义的理解

前言 最近再阅读 Linux ion&#xff08;一种内存分配管理&#xff09;时&#xff0c;遇到了 PAGE_ALIGN 宏&#xff0c;这个宏到底是怎么工作的&#xff1f; 【页对齐】时什么意思&#xff1f; 页大小就是 4096 吗&#xff1f; 追踪 PAGE_ALIGN 通过一步一步的追踪&#xff0…

基于自动化工具autox.js的抢票(猫眼)

1.看到朋友圈抢周杰伦、林俊杰演唱会票贼难信息,特研究了一段时间,用autox.js写了自动化抢票脚本,购票页面自动点击下单(仅限安卓手机)。 2.脚本运行图 3.前期准备工作 (1)autox.js社区官网:AutoX.js (2)b站上学习资料:10分钟学会AutoX.js hello world_哔哩哔哩_bi…

下一个风口在哪里?云计算:未来十年最有潜力行业!

近年来&#xff0c;中国云计算产业发展迅猛&#xff0c;保持30%以上的年均增长率&#xff0c;成为全球增速最快的市场之一&#xff0c;云计算应用领域正向制造、政务、金融、医疗、教育等企业级市场延伸拓展。 目前&#xff0c;云计算应用的普及促使开源技术广受关注&#xff…

工控安全与网络安全有什么不同?

在当代&#xff0c;全球制造业正在经历一场前所未有的技术变革。工业4.0不仅代表着自动化和数据交换的进步&#xff0c;它还揭示了工业自动化、智能制造与系统集成的融合。这种集成为企业带来了效率和质量的双重提升&#xff0c;但同时也暴露出新的安全隐患。工控系统成为了这一…

守护线程:当一个线程使用setDaemon后,便成了守护线程,当守护线程结束,其相关线程也相应结束

public static void main(String[] args) throws InterruptedException {MyDaemonThread myDaemonThread new MyDaemonThread();//如果我们希望当main线程结束后&#xff0c;子线程自动结束//我们只需要将子线程设置为守护线程即可myDaemonThread.setDaemon(true);myDaemonThr…

机械设计制造,设计行业图纸透明加密保护。防止内部终端核心文件数据、资料外泄

当下互联网时代&#xff0c;许多设计单位的设计图纸都是以电子文件的形式存在于终端电脑和服务器上。在图纸的设计生产过程中&#xff0c;必定会经过多个部门人员之手&#xff0c;此过程中就隐藏着巨大的风险。所以&#xff0c;设计单位需要使用专业的图纸加密软件来保护内部图…

设计模式(14)备忘录模式

一、介绍&#xff1a; 1、定义&#xff1a;是一种行为设计模式&#xff0c;它允许将对象的内部状态保存在一个备忘录对象中&#xff0c;并在需要时恢复对象的状态&#xff0c;而不破坏对象的封装性。 2、使用场景&#xff1a; &#xff08;1&#xff09;当需要保存和恢复对象…

xcheck插件安装到idea中

一、安装xcheck-cli 1.部署好xbox环境之后&#xff0c;在 xcheck 页面右上角&#xff0c;点击帮助进入帮助中心&#xff0c;切换至资源下载tab&#xff0c;进入资源下载页面&#xff0c;下载好所需要的版本&#xff0c;我需要的是windows版本的xcheck-cli和xcheck-idea插件 2.添…

基于ssm卤菜销售系统

基于ssm卤菜销售系统 摘要 基于SSM&#xff08;Spring、SpringMVC、MyBatis&#xff09;框架的卤菜销售系统是一种现代化的食品销售管理系统&#xff0c;旨在提高卤菜店铺的效率、准确性和客户满意度。该系统通过整合多个关键组件&#xff0c;实现了卤菜销售业务的自动化管理&a…

【完美云曦篇】新预告,云曦遭魔改被抓,石昊首秀九天劫光,反杀战王

【侵权联系删除】【文/郑尔巴金】 随着石昊进入血色平原&#xff0c;与云曦开启大逃亡&#xff0c;万众期待的完美世界动画战王之殇的全新特别篇终于要来了。官方也是相当的给力&#xff0c;直接公布了特别篇第135集的先行预告&#xff0c;石昊的雷帝新形态直接帅翻了&#xf…

使用VisualStudio生成类图结构图for高效阅读代码

使用VisualStudio高效阅读代码 前言相关准备导入工程利用VisualStudio生成类图&#xff0c;结构体调用关系利用EnterpriseArchitect(EA)画时序图 前言 目前市面上代码阅读的IDE工具非常丰富&#xff0c;也各有千秋。由于工作经历原因&#xff0c;研发机经历过windows、Mac、Li…

vscode远程连接ubuntu

修改环境变量&#xff0c;改使用git自带的ssh工具 openssh: C:\Windows\System32\OpenSSH\ssh.exeGit ssh: C:\Program Files\Git\usr\bin\ssh.exe vscode安装插件remote-ssh 重开软件&#xff0c;在左侧拓展入口下方&#xff0c;进入远程资源管理器 点击设置&#xff0c;进…

在vs2019或者vs2022中写linux C/CPP程序或者QT程序总是乱码怎么办?

第一步 第二步 第三步 成果 1)情况1 或者: 2)情况2 (好像vs2022是情况1 , vs2019是情况2) 然后这样保存就好:

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

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

Chromium源码由浅入深(一)

工作中需要对Chromium源码、尤其是源码中图形部分进行深入研究&#xff0c;所以借此机会边学习边写文章&#xff0c;分享一下我的实时学习研究Chromium源码的由浅入深的过程。 闲言少叙&#xff0c;书归正传。 通过命令行启动Chrome浏览器&#xff0c;命令及结果如下&#xf…

docker 部署tig监控服务

前言 tig对应的服务是influxdb grafana telegraf 此架构比传统的promethus架构更为简洁&#xff0c;虽然influxdb开源方案没有集群部署&#xff0c;但是对于中小型服务监控需求该方案简单高效 本文以docker-compose来演示这套监控体系的快速搭建和效果。 部署 docker-compos…

vue手动拖入和导入excel模版

1.列表按钮 系统设置的按钮权限 v-if“$hasPermission(‘om:equipmentinformation:importProblemStatistics’)” <el-button click“importExcel(scope.row.id)” size“small” type“text” v-if“$hasPermission(‘om:equipmentinformation:importProblemStatistics’)…

【lesson14】进程控制之进程等待

文章目录 为什么要有进程等待&#xff1f;如何等待和什么是等待&#xff1f; 为什么要有进程等待&#xff1f; 1.子进程退出&#xff0c;父进程不管子进程&#xff0c;子进程就要处于僵尸状态------会导致内存泄漏 2.父进程创建子进程是要让子进程办事的&#xff0c;那么子进…

AI小百科 - 什么是词向量?

如何表示一个单词的意义&#xff1f;对人来说&#xff0c;一般用解释法&#xff0c;用一段话来解释词的含义。如“太阳”在新华字典中的释义是“太阳系的中心天体。银河系的一颗普通恒星。”然而&#xff0c;这样的解释计算机是听不懂的&#xff0c;必须用更简洁的方式来对词义…

图片放大缩小时,图片上会出现蓝色蒙版解决方案

1. 问题描述&#xff1a; 页面上需要展示几张图片&#xff0c;并且有放大、缩小、旋转功能&#xff0c;在放大时&#xff0c;图片上出现了蓝色蒙版&#xff0c;如下图&#xff1a; 2. 解决方案&#xff1a; body{ -webkit-user-select:none; -moz-user-select:none; -ms-user…