vue3+pinia+vuerouter4动态路由菜单

news2024/11/19 11:24:33

文章目录

  • 前言
  • 一、用户权限和菜单列表数据
  • 二、pinia存储数据状态共享
    • 1.创建存储用户详情的user.ts文件
    • 2.创建存储用户菜单和权限的menus.ts文件
  • 三、设置动态路由
    • 1.在router文件夹下面创建routers.ts文件
    • 2.设置前置路由守卫
    • 3.左侧导航菜单


前言

最近在做一个通用后台管理系统的框架,通过用户登录拿取用户的权限和菜单列表数据来动态添加路由,使不同用户的显示不同左侧菜单列表。这篇文章主要是讲述通过vue3+router+pinia技术栈设置动态路由菜单。


🚀效果

在这里插入图片描述
💥目录

在这里插入图片描述

一、用户权限和菜单列表数据

用户登录后拿取token,请求api/sysMenu/nav接口获取的数据。如果没有后端也可以使用mock去模拟数据,这里主要讲实现思路。

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "nav": [
      {
        "id": 1,
        "title": "系统管理",
        "icon": "el-icon-s-operation",
        "path": "",
        "name": "sys:manage",
        "component": "",
        "children": [
          {
            "id": 2,
            "title": "用户管理",
            "icon": "el-icon-s-custom",
            "path": "/sys/users",
            "name": "sys:user:list",
            "component": "sys/User",
            "children": [
              {
                "id": 9,
                "title": "添加用户",
                "icon": null,
                "path": null,
                "name": "sys:user:save",
                "component": null,
                "children": []
              },
              {
                "id": 10,
                "title": "修改用户",
                "icon": null,
                "path": null,
                "name": "sys:user:update",
                "component": null,
                "children": []
              },
              {
                "id": 11,
                "title": "删除用户",
                "icon": null,
                "path": null,
                "name": "sys:user:delete",
                "component": null,
                "children": []
              },
              {
                "id": 12,
                "title": "分配角色",
                "icon": null,
                "path": null,
                "name": "sys:user:role",
                "component": null,
                "children": []
              },
              {
                "id": 13,
                "title": "重置密码",
                "icon": null,
                "path": null,
                "name": "sys:user:repass",
                "component": null,
                "children": []
              }
            ]
          },
          {
            "id": 3,
            "title": "角色管理",
            "icon": "el-icon-rank",
            "path": "/sys/roles",
            "name": "sys:role:list",
            "component": "sys/Role",
            "children": [
              {
                "id": 7,
                "title": "添加角色",
                "icon": "",
                "path": "",
                "name": "sys:role:save",
                "component": "",
                "children": []
              },
              {
                "id": 14,
                "title": "修改角色",
                "icon": null,
                "path": null,
                "name": "sys:role:update",
                "component": null,
                "children": []
              },
              {
                "id": 15,
                "title": "删除角色",
                "icon": null,
                "path": null,
                "name": "sys:role:delete",
                "component": null,
                "children": []
              },
              {
                "id": 16,
                "title": "分配权限",
                "icon": null,
                "path": null,
                "name": "sys:role:perm",
                "component": null,
                "children": []
              }
            ]
          },
          {
            "id": 4,
            "title": "菜单管理",
            "icon": "el-icon-menu",
            "path": "/sys/menus",
            "name": "sys:menu:list",
            "component": "sys/Menu",
            "children": [
              {
                "id": 17,
                "title": "添加菜单",
                "icon": null,
                "path": null,
                "name": "sys:menu:save",
                "component": null,
                "children": []
              },
              {
                "id": 18,
                "title": "修改菜单",
                "icon": null,
                "path": null,
                "name": "sys:menu:update",
                "component": null,
                "children": []
              },
              {
                "id": 19,
                "title": "删除菜单",
                "icon": null,
                "path": null,
                "name": "sys:menu:delete",
                "component": null,
                "children": []
              }
            ]
          }
        ]
      },
      {
        "id": 5,
        "title": "系统工具",
        "icon": "el-icon-s-tools",
        "path": "",
        "name": "sys:tools",
        "component": null,
        "children": [
          {
            "id": 6,
            "title": "数字字典",
            "icon": "el-icon-s-order",
            "path": "/sys/dicts",
            "name": "sys:dict:list",
            "component": "sys/Dict",
            "children": []
          }
        ]
      },
      {
        "id": 20,
        "title": "管理员",
        "icon": null,
        "path": null,
        "name": "sys:user",
        "component": "sys/Normal",
        "children": []
      }
    ],
    "authoritys": [
      "ROLE_admin",
      "ROLE_normal",
      "sys:manage",
      "sys:user:list",
      "sys:role:list",
      "sys:menu:list",
      "sys:tools",
      "sys:dict:list",
      "sys:role:save",
      "sys:user:save",
      "sys:user:update",
      "sys:user:delete",
      "sys:user:role",
      "sys:user:repass",
      "sys:role:update",
      "sys:role:delete",
      "sys:role:perm",
      "sys:menu:save",
      "sys:menu:update",
      "sys:menu:delete",
      "sys:user"
    ]
  }
}

二、pinia存储数据状态共享

tips: pinia的引入去官网看看就能上手

1.创建存储用户详情的user.ts文件

// @src/store/user.ts
import { defineStore } from 'pinia'
import { logout } from '@/api/user'
import { ref } from 'vue';

export const useUserStore = defineStore('user', () => {
  const token = ref("")
  const userInfo = ref({} as UserInfo)
  function SET_TOKEN(name: string) {
    token.value = name
    localStorage.setItem("token", name)
  }
  function SET_INFO(user: UserInfo) {
    userInfo.value = user
  }
  async function remove() {
    await logout()
    localStorage.clear()
    sessionStorage.clear()
    SET_INFO({} as UserInfo)
  }
  
  return {
    persist: true,
    token,
    userInfo,
    remove,
    SET_TOKEN,
    SET_INFO
  }
})

2.创建存储用户菜单和权限的menus.ts文件

// @src/store/menus.ts
import { defineStore } from 'pinia'
import { ref } from 'vue';

export const useMeanStore =  defineStore('mean', () => {
  // 菜单数据
  const menuList = ref([])
  // 权限数据
  const permList = ref([])
  const hasRoute = ref(false)


  function changeRouteStatus(state: any) {
    hasRoute.value = state
    sessionStorage.setItem("hasRoute", state)
  }
  function setMenuList(menus: any) {
    menuList.value = menus
  }
 function setPermList(authoritys: any) {
    permList.value = authoritys
  }
  return {
    menuList,
    permList,
    hasRoute,
    changeRouteStatus,
    setMenuList,
    setPermList
  }
})

三、设置动态路由

1.在router文件夹下面创建routers.ts文件

提示:main.ts文件下导入 import router from '@/router/routers' 然后挂载实例app.use(router)

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Index from '../views/Index.vue'
import Layout from '../layout/index.vue'
import { nav } from '@/api/system/menu'
import { useMeanStore } from '@/store/menus'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/login',
    name: 'Login',
    meta: {
      title: '登录',
      keepAlive: true,
      requireAuth: false
    },
    component: () => import('@/views/login.vue')
  },
  {
    path: '/redirect',
    name: 'Redirect',
    component: Layout,
    children: [
      {
        path: '/index',
        name: 'Index',
        meta: {
          title: "首页"
        },
        component: Index
      },
    ]
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: Index,
        name: 'Dashboard',
        meta: { title: '首页', icon: 'index', affix: true, noCache: true }
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

2.设置前置路由守卫

  • beforeEach有三个参数 to: 即将要进入的目标from: 当前导航正要离开的路由可选的第三个参数 next:进入下一个目标,beforeEach个人理解是一个路由过滤器,在路由前进行判断的处理。
// @router/routers.ts 中添加前置路由守卫
router.beforeEach((to, from, next) => {
  let token = localStorage.getItem("token")
  // 注意:在beforeEach中调用pinia存储的菜单状态是为了避免` Did you forget to install pinia?`这个bug
  const useMean = useMeanStore()
  console.log('hasRoute', useMean.hasRoute)
  if (to.path == '/login') {
    console.log("login!!!!!!!!!!!")
    next()
  } else if (!token) {
    console.log("还没有token!!!")
    next({path: "/login"})
  } else if (to.path == '/' || to.path == '') {
    next({path: '/'})
  } else if (!useMean.hasRoute) {
    nav().then(res => {
      useMean.setMenuList(res.data.nav)
      useMean.setPermList(res.data.authoritys)
      res.data.nav.forEach((menu: { children: any[]; }) => {
        if (menu.children) {
          menu.children.forEach((e: any) => {
            if (!e.component) {
              return
            }
            let route:any = {
              name: e.name,
              path: e.path,
              meta: {
                icon: e.icon,
                title: e.title
              },
              component: () =>import('../views/' + e.component+'.vue')
            }
            router.addRoute("Redirect", route)
          })
        }
      })
    })
    useMean.changeRouteStatus(true)
    next({path: to.path})
  } else {
    console.log("已经有路由了------------")
    next()
  }
})

✨完整@router/routers.ts代码

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Index from '../views/Index.vue'
import Layout from '../layout/index.vue'
import { nav } from '@/api/system/menu'
import { useMeanStore } from '@/store/menus'



const routes: Array<RouteRecordRaw> = [
  {
    path: '/login',
    name: 'Login',
    meta: {
      title: '登录',
      keepAlive: true,
      requireAuth: false
    },
    component: () => import('@/views/login.vue')
  },
  {
    path: '/redirect',
    name: 'Redirect',
    component: Layout,
    children: [
      {
        path: '/index',
        name: 'Index',
        meta: {
          title: "首页"
        },
        component: Index
      },
    ]
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: Index,
        name: 'Dashboard',
        meta: { title: '首页', icon: 'index', affix: true, noCache: true }
      }
    ]
  }

]

const router = createRouter({
  history: createWebHistory(),
  routes
})

router.beforeEach((to, from, next) => {
  let token = localStorage.getItem("token")
  const useMean = useMeanStore()
  console.log('hasRoute', useMean.hasRoute)
  if (to.path == '/login') {
    console.log("login!!!!!!!!!!!")
    next()
  } else if (!token) {
    console.log("还没有token!!!")
    next({path: "/login"})
  } else if (to.path == '/' || to.path == '') {
    next({path: '/'})
  } else if (!useMean.hasRoute) {
    nav().then(res => {
      useMean.setMenuList(res.data.nav)
      useMean.setPermList(res.data.authoritys)
      res.data.nav.forEach((menu: { children: any[]; }) => {
        if (menu.children) {
          menu.children.forEach((e: any) => {
            if (!e.component) {
              return
            }
            let route:any = {
              name: e.name,
              path: e.path,
              meta: {
                icon: e.icon,
                title: e.title
              },
              component: () =>import('../views/' + e.component +'.vue')
            }
            router.addRoute("Redirect", route)
          })
        }
      })
    })
    useMean.changeRouteStatus(true)
    next({path: to.path})
  } else {
    console.log("已经有路由了------------")
    next()
  }
  
})



export default router

描述:通过前置路由守卫拦截路由处理,在登录页判断是否有token,如果没有token继续去登陆,登录成功后拿取token使用pinia存储状态,再判断是否有路由hasRoute没有就请求api/sysMenu/nav接口获取的数据,添加路由并存储菜单数据和权限数据到pinia状态中共享数据。

3.左侧导航菜单

beforeEach中已经把用户菜单和权限存储到@src/store/menus.ts中,在Sidebar.vue中引用import { useMeanStore } from ‘@/store/menus’ 拿取菜单数据渲染菜单列表

<template>
    <div>
        <el-menu
            active-text-color="#ffd04b"
            background-color="#304156"
            class="el-menu-vertical-demo"
            default-active="2"
            text-color="#fff"
            @open="handleOpen"
            @close="handleClose"
        >
            <el-sub-menu default-active="Index" :index="menu.name" v-for="menu in menuList" :key="menu.name">
                <template #title>
                    <el-icon><location /></el-icon>
                    <span>{{menu.title}}</span>
                </template>
                <router-link :to="item.path" v-for="item in menu.children" :key="item.name">
                  <el-menu-item :index="item.name">
                    <template #title>
                      <i :class="item.icon"></i>
                      <span slot="title">{{item.title}}</span>
                    </template>
                  </el-menu-item>
                </router-link>
            </el-sub-menu> 
        </el-menu>
    </div>
</template>

<script setup lang="ts">
import { reactive } from 'vue';
import { useMeanStore } from '@/store/menus';
import { storeToRefs } from "pinia";

const useMean = useMeanStore()
const { menuList, permList, hasRoute } = storeToRefs(useMean);
const setMenuList:any = localStorage.getItem("setMenuList")
console.log('setMenuList', menuList)

const handleOpen = (key: string, keyPath: string[]) => {
    console.log(key, keyPath);
};
const handleClose = (key: string, keyPath: string[]) => {
    console.log(key, keyPath);
};
</script>

<style lang="scss" scoped>
</style>

动态路由设置完成效果就会如上效果图

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

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

相关文章

35.JavaScript对象和数组的解构赋值基础详解、let陷阱、函数参数解构

文章目录35.JavaScript对象和数组的解构赋值数组解构解构不改变原数组忽略数组元素可迭代对象使用解构赋值给任何变量与.entries()方法结合与Map结合变量交换多余的元素对象解构属性变量映射默认值多余的属性let陷阱多层解析函数参数解析总结35.JavaScript对象和数组的解构赋值…

HTML使用Element-UI制作管理系统页面(无需脚手架以及创建vue工程)

HTML正常使用Element-UI前言尝试经历设计附件前言 入职培训到Web前端后布置了一个制作管理系统前端页面的任务&#xff0c;任务要求包含&#xff1a; 1.页面的布局主要为左侧导航菜单栏&#xff0c;右侧为信息展示栏&#xff0c;要体现嵌套 2.点击菜单栏切换右侧信息展示时左侧…

前端在项目中使用mockjs模拟数据的增删改查

背景 在项目开发时&#xff0c;会存在前端界面已经画好了但是后端接口还在开发的情况&#xff0c;此时前端可以先根据接口文档明确自己需要的字段&#xff0c;然后使用mock模拟后端接口进行调试 安装 npm install mockjs 使用 1. 创建vue项目之后&#xff0c;新建一个mock…

基于SpringBoot的校园疫情防控系统设计与实现

1.概述 校园疫情防控系统的开发运用java技术、springboot框架&#xff0c;MIS的总体思想&#xff0c;以及Mysql等技术的支持下共同完成了该系统的开发&#xff0c;实现了校园疫情防控管理的信息化&#xff0c;使用户体验到校园疫情防控管理&#xff0c;管理员管理操作将更加方…

使用 el-table 实现树形数据懒加载、点击行展开、每次只展示一条数据(大类)以及自定义表格合计值

1. 使用 el-table 实现树形数据懒加载 实现必需条件&#xff1a; lazy :load“loadNode” :tree-props“{ children: ‘children’, hasChildren: ‘hasChildren’ }” 注意&#xff1a;特别是第3条&#xff0c;后端接口必须传给你"hasChildren"(名字可以不一样)…

Vue3的vue-router超详细使用

从零开始搭建Vue3环境&#xff08;vitetsvue-router&#xff09;&#xff0c;手拉手做一个router项目搭建vue3环境vue-router入门&#xff08;宝宝模式&#xff09;vue-router基础&#xff08;青年模式&#xff09;一。动态路由匹配1.带参数的动态路由匹配2.捕获所有路由或404 …

uniapp项目中引入vant-Weapp(局部全局都有 史上最详细的方法)

1.先在根目录创建wxcomponents文件夹 2.打开 https://github.com/youzan/vant-weapp 下载最新的vant-Weapp 3.把我们下好的文件vant-weapp里面只留下dist其余的可以全部删掉&#xff0c;然后把vant-weapp放到 wxcomponents里面 4.在App.vue引入vant样式 import /wxcomponents…

蓝桥杯web开发-5道模拟题让你信心满满

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域新星创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

最好的Vue组件库之Vuetify的入坑指南(持续更新中)

目录 安装Vuetify 文档结构 快速入门 特性 样式和动画 首先先声明&#xff0c;个人不是什么很牛逼的大佬&#xff0c;只是想向那些想入坑Vuetify的前端新手或者嫌文档太长不知如何入手的人提供一些浅显的建议而已&#xff0c;能让你们稍微少走一些弯路就是本文的目的。我其实也…

『从零开始学小程序』媒体组件video组件

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位喜欢写作&#xff0c;计科专业大三菜鸟 &#x1f3e1;个人主页&#xff1a;starry陆离 &#x1f552;首发日期&#xff1a;2022年9月15日星期四 如果文章有帮到你的话记得点赞&#x1f44d;收藏&#x1f497;支持一下哦 『…

Vue结合高德地图实现HTML写自定义信息弹窗

最近在写项目的时候有个需求就是根据点击地图上的点展示对应的信息&#xff0c;弹窗看着还挺花哨的。我在高德地图官网上还有各大平台找了如何自定义弹窗&#xff0c;可给出的大多数都是通过JS写HTML结构&#xff0c;我感觉这种不仅不好布局&#xff0c;而且可读性和维护性都不…

客户端会话跟踪技术 Cookie 浅谈

文章目录前言为什么之前浏览器和服务器不支持数据共享&#xff1f;会话跟踪技术Cookie的概念Cookie的工作流程Cookie的基本使用Cookie原理分析Cookie的存活时间Cookie存储中文前言 用户打开浏览器&#xff0c;第一次访问 Web 服务器资源时&#xff0c;会话建立&#xff0c;直到…

富文本编辑器Quill 介绍及在Vue中的使用方法

在Web开发中&#xff0c;富文本编辑器是不可或缺的一个功能组件&#xff0c;掌握少量基础语法就能让一篇文章实现较为不错的排版效果&#xff0c;即见即所得。 目前市场上已提供大量集成富文本编辑器的包&#xff0c;Quill 作为其中一个&#xff0c;以简单、易上手特点&#x…

vue项目打包失败问题记录

项目"vue": "^2.7.14"版本 起因&#xff1a;项目里安装了openlayers最新版本的地图插件&#xff0c;打包会成功&#xff0c;但是打包页面会有红色提示 刚开始根据红色提示百度找到相同错误的方法提供了的一系列提示安装啊&#xff0c;卸载&#xff0c;装了…

【WebSocket 协议】Web 通信的下一步进化

标题【手动狗头&#x1f436;】&#xff0c;大佬轻饶 目录一、什么是 WebSocket ?二、WebSocket 应用场景?三、代码中的 WebSocket四、一个完美的案例&#xff1a;在线聊天程序实现服务器chat/index.js实现客户端chat/index.htmlchat/style.css最终效果WebSocket 是基于单个 …

关于elementUI表单的清除验证以及复合型输入框

目录 一、清除表单的验证 问题的发生以及解决过程 代码 总结 二、复合型输入框——查询&#xff08;前置和后置都有的&#xff09; 问题的发生以及解决过程 代码 展示 一、清除表单的验证 问题的发生以及解决过程 表单弹窗关闭后再打开会出现上一次的验证信息提示&am…

JS中如何判断一个值是否为Null

前言 在鉴别JavaScript原始类型的时候我们会用到typeof操作符。Typeof操作符可用于字符串、数字、布尔和未定义类型。但是你运行typeof null时&#xff0c;结果是“object”(在逻辑上&#xff0c;你可以认为null是一个空的对象指针&#xff0c;所以结果为“object”)。 如何判…

Vue3【计算属性、Class绑定、Style绑定 、侦听器、表单输入绑定、模板引用、组件注册方式、组件嵌套关系 、组件注册方式】(三)-全面详解(学习总结---从入门到深化)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…

一个小项目带你了解vue框架——TodoList(简单实用易上手)

写在前面 你是否还在为繁杂的事情感到头昏脑涨&#xff1f;你是否还在将便利贴贴满整个桌面&#xff1f;本文就为你解决这个烦恼&#xff0c;使用vue框架做一个TodoList&#xff0c;将事情整理的井井有条不再是一个遥不可及梦&#xff01;让我们行动起来吧&#xff01; 基于vue…

解决前端项目问题,uniapp运行微信开发工具小程序,出现× initialize报错,以及浏览器无法运行

项目场景&#xff1a; uniapp进行小程序以及多端web页面都不知道如何配置讲项目运行起来。 就会报出无法运行错误。 [微信小程序开发者工具] - initialize [微信小程序开发者工具] [微信小程序开发者工具] IDE may already started at port , trying to connect如图 问题描…