vue实现动态路由菜单!!!

目录

  • 总结
  • 一、步骤
    • 1.编写静态路由
      • 编写router.js
      • main.js注册
    • 2.编写permisstions.js权限文件
      • 编写permisstions.js
      • axios封装的API
        • store.js状态库
        • system.js Axios-API
        • request.js axios请求实例封装
    • 3.编写菜单树组件
      • MenuTree.vue
    • 4.主页中使用菜单树组件


总结

递归处理后端响应的菜单树,后依次通过addRoute方法往静态父路由,添加动态子路由,添加完使用el-menu渲染并添加router属性实现路由菜单模式
addRoute:https://router.vuejs.org/zh/api/interfaces/Router.html#Methods-addRoute

后端数据库树菜单:
在这里插入图片描述

一、步骤

1.编写静态路由

  • 创建router.js文件默认导出静态路由,后在main.js加载注册

编写router.js

//静态路由配置文件
// eslint-disable-next-line no-unused-vars
import Router from "vue-router"
// eslint-disable-next-line no-unused-vars
import Vue from "vue"
//在Vue中加载路由模块
Vue.use(Router)

//写路由表
// eslint-disable-next-line no-unused-vars
// const Foo = { template: '<div>foo</div>' }
const routes = [
    // 进入vue项目默认进入登录页面
    {
        path: "/",
        redirect: "/Login"
    },
    {
        path: "/Login",
        component: () => import("../view/Login"),
        meta: {
            skipAuthCheck: true // 添加一个标记,表示不需要进行身份验证检查
        }
    },
    {
        path: "/index",
        name: 'index',
        component: () => import("../components/index"),
        children: [
            // 默认显示hello页面
            {
                path: "/",
                redirect: "/hello"
            },
            {
                path: "/hello",
                meta: { requiresAuth: true },
                component: () => import("../components/hello"),
            },
        ],
    },
]

export default new Router({
    routes
});


// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
let routerReplace = Router.prototype.replace;
// push
Router.prototype.push = function push(location) {
    return routerPush.call(this, location).catch(err => err)
}
// replace
Router.prototype.replace = function push(location) {
    return routerReplace.call(this, location).catch(err => err)
}

main.js注册

import Vue from 'vue'
import App from './App.vue'
//引入一个router模块
import router from "@/router/router"
import routers from "@/router/permissions"
import element from 'element-ui';
import axiosInstance from '@/request/request'
import { createPinia } from 'pinia';
import 'element-ui/lib/theme-chalk/index.css';
// 在生产环境中禁用警告信息和启用构建优化
Vue.config.productionTip = false

// 将 Axios 实例添加到 Vue 原型中,以便在组件中使用
// Vue.prototype.axios axios便在组件中使用如:this.$axios
Vue.prototype.axios = axiosInstance

const pinia = createPinia();
Vue.use(pinia)

Vue.use(element)
new Vue({
  router,
  routers,
  render: h => h(App),
}).$mount('#app')

2.编写permisstions.js权限文件

  • 结合axios封装API于permisstions中配置的全局前置守卫中获取菜单树存入sessionStorage缓存

编写permisstions.js

// 导入默认导出的路由对象用于跳转路由
// import router from '@/router/router';
//导入路由表
import routers from "@/router/router"
//路由配置文件
import { tokenStore } from "@/store/store"

// 全局前置守卫
// to当前即将要进入的路由对象
routers.beforeEach((to, from, next) => {
    //如果当前的访问的请求是Login放行
    if (to.path === '/Login') {
        next();
    }
    else {
        //其余访问请求判断用户是否登录
        if (!isLoggedIn()) {
            console.log("抱歉你未登录");
            next('/Login'); // 如果用户未登录,则重定向到登录页面
        } else {
            // console.log(to);
            next();
        }
    }

})
//登录验证函数
function isLoggedIn() {
    console.log("进入路由守卫");
    // 在这里实现检查用户是否已登录的逻辑,例如检查是否有有效的令牌或会话
    // 如果已登录,返回true,否则返回false
    const jwtToken = sessionStorage.getItem('jwtToken'); // 从本地缓存中获取会话信息
    // console.log(jwtToken);
    let userId = sessionStorage.getItem('user_name_id');


    //userId存在获取动态路由信息
    if (userId && jwtToken) {
        // if (tokenStore().flag) {
            tokenStore().getRouters(userId).then(
                (res) => {
                    if (res.status == 201) {
                        // console.log(res.data);
                        //动态路由源信息
                        let r = res.data;

                        // 过滤动态路由菜单
                        let menu = fnAddDynamicMenuRoutes(r)
                        console.log(menu);

                        menu.forEach(element => {
                            element.children.forEach(s => {
                                // console.log(s);
                                //index为父路由的name属性值  s是需添加的路由
                                routers.addRoute('index', s);
                            })
                        });
                        // console.log(routers);
                        // 动态路由得到后修改标记为false表示已执行过无需在执行
                        tokenStore().flag = false;
                        // 保存路由到会话
                        sessionStorage.setItem('menu', JSON.stringify(menu));
                    }
                    if (res.status == 501) {
                        //未获取到动态路由重新登录
                        routers.push("/Login");
                    }
                }
            )
        // }
    }
    return jwtToken && routers; // 如果登录令牌存在,则用户已登录
}

// 用于处理动态菜单数据,将其转为 route 形式
function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
    // 用于保存普通路由数据
    let temp = []
    // 用于保存存在子路由的路由数据
    let route = []
    // 遍历数据
    for (let i = 0; i < menuList.length; i++) {
        // 存在子路由,则递归遍历,并返回数据作为 children 保存
        if (menuList[i].childMenus && menuList[i].childMenus.length > 0) {
            // 获取路由的基本格式
            route = getRoute(menuList[i])
            // 递归处理子路由数据,并返回,将其作为路由的 childMenus 保存
            route.children = fnAddDynamicMenuRoutes(menuList[i].childMenus)
            // 保存存在子路由的路由
            routes.push(route)
        } else {
            // 保存普通路由
            temp.push(getRoute(menuList[i]))
        }
    }
    // 返回路由结果
    return routes.concat(temp)
}

// 返回路由的基本格式
function getRoute(item) {
    // 路由基本格式
    let route = {
        // 路由的路径
        path: item.path,
        // 路由名
        name: item.menuName,
        // 路由所在组件  必须有一段已定义好的组件名字
        // component: (resolve) => require([`@/layout/Index`], resolve),
        component: (resolve) => require([`../components${item.menuUrl}.vue`], resolve),
        meta: {
            id: item.menuType,
            // icon: item.icon
        },
        // 路由的子路由
        children: []
    }
    // 返回 route
    return route
}


export default routers

axios封装的API

store.js状态库
// 导入pinia库
import { defineStore } from 'pinia';
// 导入api
import { login, logOut, getRouters } from '@/request/api/system';
// 导入jwt解析器
import jwtDecode from "jwt-decode";
// 导入默认导出的路由对象用于跳转路由
import router from '@/router/router';

export const tokenStore = defineStore({
  id: 'myStore',
  state: () => ({
    jwtToken: null,
    user_name: null,
    user_name_id: null,
    user_type: null,
    menu: null,
  }),
  actions: {
    getRouters(userId) {
      return new Promise((resolve) => {
        getRouters(userId).then(res => {
          console.log(res);
          resolve(res)
        })
      })
    },
    doLogin(params) {
      login(params).then((res) => {
        if (res.status == 200) {
          const jwtToken = res.data; // 从响应中获取JWT
          sessionStorage.setItem('jwtToken', jwtToken);
          this.jwtToken = jwtToken; // pinia存储JWT

          // 解码JWT令牌以获取载荷信息
          const decodedToken = jwtDecode(jwtToken);
          console.log(decodedToken);

          //访问包含在JWT令牌中的用户信息
          //保存用户类型的id便于门诊医生问诊
          var user_name_id = decodedToken.user_name_id;
          sessionStorage.setItem('user_name_id', user_name_id);
          this.user_name_id = user_name_id;
          //保存用户类型便于控制导航栏的显示与隐藏
          const userType = decodedToken.user_type;

          this.user_type =
            userType == 1
              ? "系统管理员"
              : userType == 2
                ? "挂号员"
                : "门诊医生";
          //跳转到主页
          router.push("/index");
        }
      });
    },
    LogOut() {
      return logOut();
    }
  },
});

system.js Axios-API
import axiosInstance from "@/request/request"

export function login(data) {
    return axiosInstance({
        url : "/Login",
        method : "POST",
        data
    })
}

export function logOut() {
    return axiosInstance({
        url : "/LogOut",
        method : "get",
    })
}

export function getUserInfo(data) {
    return axiosInstance({
        url : "/User/select",
        method : "post",
        data
    })
}

export function getRouters(userId) {
    return axiosInstance({
        url : `/UserTreeInfo${userId}`,
        method : "get",
    })
}
request.js axios请求实例封装
import axios from 'axios'
import { Message } from 'element-ui'
import {tokenStore} from "@/store/store";

// 创建一个 Axios 实例
const axiosInstance = axios.create({
    baseURL: 'http://localhost:8080/qy', // 通用后端 Url 地址
    timeout: 5000, // 请求最大等待时间,
    headers: { 'Content-Type': 'application/json' },
})

// 添加请求拦截器
axiosInstance.interceptors.request.use(
    (config) => {
        // 获取请求的URL
        const requestUrl = config.url;
        console.log(requestUrl);
        // console.log(config);
        // 提取URL路径部分/qy/Login
        // const urlPath = new URL(requestUrl).pathname;

        // 如果是post请求将参数data转成json字符串
        // 检查请求方法是否为 POST
        if (config.method === 'post' || config.method === 'POST') {
            // 将请求数据转换为 JSON 字符串
            config.data = JSON.stringify(config.data);
        }

        if (config.method === 'get' || config.method === 'GET') {
            config.headers['Content-Type'] = 'x-www-form-urlencoded';
        }

        // 在请求头中获取令牌信息
        const jwtToken = tokenStore().jwtToken // 从pinia中获取令牌

        // 检查是否是登录请求,这里假设登录请求的URL是 '/Login'
        if (requestUrl !== '/Login' && requestUrl !== '/LogOut') {
            console.log(requestUrl);
            // 如果不是登录请求,添加令牌到请求头
            if (jwtToken) {
                config.headers.Authorization = `${jwtToken}`
            }
        }
        return config
    },
    (error) => {
        return Promise.reject(error)
    }
)

//添加响应拦截器
axiosInstance.interceptors.response.use((response) => {
    var res = response.data
    // console.log(res);
    // 设置请求状态弹窗提示
    if (res.status == 200) {
        //请求成功提示
        Message.success(res.msg);
    }
    else if(res.msg === "菜单载入成功") {
        return res
    }
    else {
        Message.error(res.msg);
    }
    // 后端响应Resbody的data数据
    return res
},
    (error) => {
        return Promise.reject(error)
    }
)


export default axiosInstance

3.编写菜单树组件

  • 接受父组件菜单树,递归遍历渲染树菜单

MenuTree.vue

<template>
  <div>
    <!-- 
        :default-active 一进页面默认显示的页面
        unique-opened 保持一个子菜单的打开
        router 采用路由模式 菜单上的index就是点击跳转的页面
        text-color 菜单文字的颜色
        active-text-color 菜单激活后文字的颜色
      -->
    <el-menu
      default-active
      background-color="#2b333e"
      router
      text-color="#fff"
      active-text-color="#ffd04b"
    >
      <template v-for="item in menuData">
        <el-submenu
          v-if="item.children && item.children.length > 0"
          :key="item.id"
          :index="item.path"
        >
          <template slot="title">
            <i class="el-icon-menu"></i>
            <span>{{ item.name }}</span>
          </template>
          <!-- 若有子菜单递归渲染 -->
          <menu-tree :menuData="item.children" />
        </el-submenu>
        <el-menu-item v-else :key="item.id" :index="item.path">{{
          item.name
        }}</el-menu-item>
      </template>
    </el-menu>
  </div>
</template>

<script>
export default {
  props: {
    menuData: {},
  },
  name: "MenuTree",
};
</script>

4.主页中使用菜单树组件

  • 导入组件并注册MenuTree.vue,通过JSON.parse()转换菜单树对象menuData,后父传子menuData渲染菜单树
<!-- eslint-disable vue/multi-word-component-names -->
<template>
  <!-- 整个页面 -->
  <div class="index">
    <!-- 左导航 -->
    <div
      class="leftNav"
      :style="{
        width: leftNavWidth,
        visibility: show,
        transition: transitionParam,
      }"
    >
      <!-- 标题 -->
      <h2 style="color: #fff; margin: 20px 0">青芽在线医疗</h2>
      <!-- 动态导航 -->
      <!-- {{ menuData }} -->
      <menu-tree :menuData="menuData"></menu-tree>
    </div>

    <!-- 主界面 -->
    <div
      class="mainSection"
      :style="{ width: mainSectionWidth, transition: transitionParam }"
    >
      <!-- 标题头部 -->
      <div class="QYheader">
        <span class="el-icon-s-operation" @click="controlWidth"></span>
        <span class="QYheaderFont"
          ><el-button type="primary" @click="LogOut">退出登录</el-button></span
        >
        <div class="QYheaderRight">
          <span class="el-icon-user-solid"></span>
          <span class="QYheaderRightFont">{{ user_name }}</span>
        </div>
      </div>
      <!-- 二级路由部分 -->
      <div class="QYcontent">
        <router-view></router-view>
      </div>
      <!-- QYcontent -->
    </div>
    <!-- mainSection -->
  </div>
  <!-- index -->
</template>

<script>
import { tokenStore } from "@/store/store";
import MenuTree from "../components/MenuTree.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "mainSection",
  components: {
    MenuTree,
  },
  data() {
    return {
      menuData: JSON.parse(sessionStorage.getItem("menu")),
      tokenStore: tokenStore(),
      //接收从Login页传来的登录用户名
      user_name: tokenStore().user_name,
      //接收从Login页传来的用户类型
      user_type: tokenStore().user_type,
      //设置导航和主界面默认宽高
      leftNavWidth: "16%",
      mainSectionWidth: "84%",
      show: "visible",
      transitionParam: "width 0.5s ease",
    };
  },
  methods: {
    //控制导航和主界面的宽和高
    controlWidth() {
      console.log("已进入控制宽度方法");
      this.leftNavWidth = this.leftNavWidth === "16%" ? "0%" : "16%";
      //控制左导航的显示与隐藏  同时设置mainSectionWidth的宽和高
      if (this.leftNavWidth === "16%") {
        this.show = "visible";
        this.mainSectionWidth = "84%";
      } else if (this.leftNavWidth === "0%") {
        this.show = "hidden";
        this.mainSectionWidth = "100%";
      }
    },

    LogOut() {
      // 删除所有本地缓存包括令牌信息
      // localStorage.clear();
      this.tokenStore.LogOut().then((res) => {
        if (res.status == 200) {
          // 删除所有本地缓存包括令牌信息
          sessionStorage.clear();
          // 重置获取路由的标记
          tokenStore.flag = false;
          // 跳转到登录页面
          this.$router.push({ path: "/Login" });
        }
      });

      // localStorage.removeItem("user_name");
      // localStorage.removeItem("user_type");
    },
  },
};
</script>

<style>
@import url(../css/index.css);
</style>

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

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

相关文章

5面试题--redis

慢查询⽇志&#xff1a;⽤于记录执⾏时间超过给定时⻓的命令请求&#xff0c;⽤户可以通过这个功能产⽣的⽇志来监视和 优化查询速度。 布隆过滤器&#xff1a;⼆进制数组进⾏存储&#xff0c;若判断元素存在则可能实际存在&#xff0c;若判断不存在则⼀定不存在。 redis中inc…

231128 刷题日报

值班刷题的第二天&#xff0c;早上地铁上看了一道题&#xff0c;以为很简单 LCR 019. 验证回文串 II 我的思路是引入计数器左右指针&#xff0c;然而 Leetcode老哥提醒了我&#xff1a; 你看看这个字符串“lcuxxucul”&#xff0c;你的默认优先删除左边&#xff0c;但是删除…

SystemVerilog 入门

文章目录 包定义SystemVerilog 数据类型结构体 SystemVerilog 过程块可嵌套模块接口 System Verilog 的优点 提高了硬件建模能力、编码效率和抽象能力&#xff1b;RTL 级、系统级行为描述&#xff1b; 增强了验证能力和为大规模复杂设计编写有效、无竞争测试程序的断言功能&am…

ESP32-Web-Server 实战编程-使用文件系统建立强大的 web 系统

ESP32-Web-Server 实战编程-使用文件系统建立强大的 web 系统 概述 在前述章节我们讲述了在网页端控制多个 GPIO 的案例。当程序开始变得复杂&#xff0c;让一些功能“自动起来”是一个好的选择。 在前面的示例中&#xff0c;我们需要在后端为每个前端代码的 URL 指定一个对…

类和对象——(2)类

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 只虽然夜晚很长&#xff0c;但天…

选择排序以及改进方案

选择排序以及改进方案 介绍&#xff1a; 选择排序是一种简单直观的排序算法&#xff0c;它的基本思想是在未排序序列中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;然后将其放在已排序序列的末尾。选择排序的过程就像是每次从待排序的元素中选择最小的一个&…

C51--4G模块

EC03-DNC&#xff1a;4G通信模块 EC03-DNC 功能特点&#xff1a; 采用最新4G CAT1方案&#xff1b; 支持数据透明传输; 支持TCP、UDP 网络协议; 支持心跳包、注册包功能最大支持64个字节数&#xff1b; 支持MQTT协议&#xff0c;支持接入OneNet平台、百度云平台、阿里云平台的…

计网Lesson4 - 计算机组网模型

文章目录 计算机的连接方式1. 两台计算机的互联2. 多台计算机的互联&#xff08;旧式&#xff09;3. 多台计算机的互联 --- 集线器&#xff08;Hub&#xff09;4. 网桥5. 多台计算机的互联 --- 交换器&#xff08;Switch&#xff09; 计算机的连接方式 1. 两台计算机的互联 网…

ELK日志收集系统-filbeat

filebeat日志收集工具 elk&#xff1a;filebeat日志收集工具和logstash相同 filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动时使用的资源要小的多 filebeat可以运行在非Java环境&#xff0c;它可以代理logstash在非java环境上收集日志…

Boot工程快速启动【Linux】

Boot工程快速启动【Linux】 在idea中打包cd usr/在local文件夹下mkdir app进入app文件夹把打包好的文件&#xff08;只上传其中的jar&#xff09;上传到app文件下检查linux中的Java版本&#xff0c;保证和项目的Java 版本保持一致运行 java -jar sp补全***.jar想看效果得查询当…

大模型的开源闭源

文章目录 开源&闭源开源和闭源的优劣势比较开源和闭源对大模型技术发展的影响开源与闭源的商业模式比较国内的大模型开源和闭源的现状和趋势 开源和闭源&#xff0c;两种截然不同的开发模式&#xff0c;对于大模型的发展有着重要影响。 开源让技术共享&#xff0c;吸引了众…

爬虫如何确定HTTP代理IP是否符合自己业务需求?

HTTP代理在许多业务场景中发挥着关键作用&#xff0c;但要确保其能够满足业务需求&#xff0c;需要考虑多个方面的因素。今天我们一起看看&#xff0c;要如何判断HTTP代理是否适合自己的业务&#xff0c;以及在选择HTTP代理时需要考虑的综合因素。 1. 稳定性 稳定性是HTTP代理…

LangChain 14 SequencialChain链接不同的组件

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

计网Lesson3 - 计算机网络评价指标与封包解包

文章目录 计算机网络的性能指标1. 速率2. 带宽3. 吞吐量4. 时延5. 时延带宽积6. 往返时间7. 利用率8. 数据的解包和封包 计算机网络的术语实体![实体](https://img-blog.csdnimg.cn/direct/cbf4ca9ed5ab4df290b5a17b4642c6a1.png)协议服务 计算机网络的性能指标 1. 速率 数据…

MOSFET安全工作区域SOA

Safe Operating Area&#xff08;SOA&#xff09;即安全工作区&#xff1a;描述了当MOSFET工作在饱和区时可以处理的最大功率。超出安全工作区&#xff0c;则可能导致元件损坏。 SOA分为五个单独的界限&#xff0c;分别是RDS(on)限制 On Resistance&#xff08;RDS(on)&#…

算法通关第十三关-青铜挑战数学基础问题

数组元素积的符号 描述 : 已知函数 signFunc(x) 将会根据 x 的正负返回特定值&#xff1a; 如果 x 是正数&#xff0c;返回 1 。如果 x 是负数&#xff0c;返回 -1 。如果 x 是等于 0 &#xff0c;返回 0 。 给你一个整数数组 nums 。令 product 为数组 nums 中所有元素值的…

大语言模型概述(二):基于亚马逊云科技的研究分析与实践

上期介绍了大语言模型的定义和发展历史&#xff0c;本期将分析基于亚马逊云科技的大语言模型相关研究方向&#xff0c;以及大语言模型的训练和构建优化。 大语言模型研究方向分析 Amazon Titan 2023 年 4 月&#xff0c;亚马逊云科技宣布推出 Amazon Titan 大语言模型。根据…

【SpringCloud系列】@FeignClient微服务轻舞者

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Vue3+java开发组队功能

Vue3java开发系统组队功能 需求分析 创建用户可以创建一个队伍&#xff08;一个房间队长&#xff09;&#xff0c;设置队伍人数&#xff0c;队伍名称&#xff08;标题&#xff09;&#xff0c;描述&#xff0c;超时时间。搜索加入&#xff0c;用户可以加入未满的队伍&#xf…

iconfont 使用彩色图标

1、下载iconfont到本地 2、全局安装 iconfont-tools npm install -g iconfont-tools 3、在iconfont解压目录下执行命令、一直回车 iconfont-tools 4、文件拷贝 执行完上述命令后会生成iconfont-weapp目录&#xff0c;将iconfont-weapp目录下的iconfont-weapp- icon.css文件…