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

news2024/11/19 20:36:10

目录

  • 总结
  • 一、步骤
    • 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>

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

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

相关文章

java基础-IO

1、基础概念 1.1、文件(File) 文件的读写可以说是开发中必不可少的部分&#xff0c;因为系统会存在大量处理设备上的数据&#xff0c;这里的设备指硬盘&#xff0c;内存&#xff0c;键盘录入&#xff0c;网络传输等。当然这里需要考虑的问题不仅仅是实现&#xff0c;还包括同步…

人工智能|机器学习——机器学习如何判断模型训练是否充分

一、查看训练日志 训练日志是机器学习中广泛使用的训练诊断工具&#xff0c;每个 epoch 或 iterator 结束后&#xff0c;在训练集和验证集上评估模型&#xff0c;并以折线图的形式显示模型性能和收敛状况。训练期间查看模型的训练日志可用于判断模型训练时的问题&#xff0c;例…

IOC DI入门

1.加上Component&#xff0c;控制翻转&#xff0c;将service和dao都交给IOC容器管理&#xff0c;成为IOC容器中的bean。用哪个类就在哪个类上面加component。 2.加上autowired。依赖注入。controller依赖于service&#xff0c;service依赖于dao。加上时&#xff0c;IOC容器会提…

Taro3+Vue3重构Mpvue小程序项目踩坑记

1、Taro小程序编译时报错&#xff1b; 原因:页面中存在小程序识别不了的标签&#xff1b;如div解决方法&#xff1a; 将div标签替换成小程序可识别的标签&#xff1b; 安装Taro中提供的插件:tarojs/plugin-html, 使其可被识别&#xff1b; 插件安装教程参考Taro官网&#xff1…

Matlab 点云曲率计算(之二)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经讨论过许多关于计算曲率的问题,这里使用一个通过拟合三次曲面方程的方式来计算曲率,计算过程如下图所示: 二、实现代码 %********

springboot+bootstarp+jsp房屋租赁系统ssm_t65a9

小型房屋租赁系统主要有管理员、房东和租户三个功能模块。以下将对这三个功能的作用进行详情的剖析。 管理员模块&#xff1a;管理员是系统中的核心用户&#xff0c;管理员登录后&#xff0c;可以对后台系统进行管理。主要功能有个人中心、房东管理、租户管理、房源城市管理、房…

数据库应用:MongoDB 库与集合管理

目录 一、理论 1.MongoDB用户管理 2.MogoDB库管理 3.MogoDB集合管理 二、实验 1.MongoDB用户管理 2.MogoDB库管理 3.MogoDB集合管理 三、问题 1.不显示新创建的数据库 2.插入数据报错 3.删除指定数据库报错 一、理论 1.MongoDB用户管理 (1) 内置角色 数据库用户…

什么是高级语言、机器语言、汇编语言?什么是编译和解释?

1、高级语言 计算机程序是一种让计算机执行特定任务的方法。程序是由程序员用一种称为编程语言的特殊语言编写的。编程语言有很多种&#xff0c;例如 C、C、Java、Python 等。这些语言被称为高级语言&#xff0c;因为它们更接近人类的自然语言&#xff0c;而不是计算机能够直接…

【LeetCode刷题】--38.外观数列

38.外观数列 方法&#xff1a;遍历生成 该题本质上是依次统计字符串中连续相同字符的个数 例如字符串 1112234445666我们依次统计连续相同字符的个数为: 3 个连续的字符 1, 222 个连续的 2&#xff0c;1 个连续的字符 3&#xff0c;3个连续的字符 4&#xff0c;1个连续的字符…

创建一个带有背景图层和前景图层的渲染窗口

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example demo解决问题&#xff1a; 创建一个带有背景图层和前景图层的渲染窗口&#xff0c;知识点&#xff1a;1. 画布转image&#xff1b;2. 渲染图层设置&#xff1b;3.…

如何高效批量生成条形码?

条形码作为商品、库存和信息管理的基础工具&#xff0c;扮演着至关重要的角色。为了满足用户对于高效、专业、多样化的条形码生成需求&#xff0c;我们推出了一款专业高效的在线条形码生成工具。 网址&#xff1a;https://www.1txm.com/ 多样化条形码支持 易条形支持多种常见…

Django请求生命周期流程

浏览器发起请求。 先经过网关接口&#xff0c;Django自带的是wsgiref&#xff0c;请求来的时候解析封装&#xff0c;响应走的时候打包处理&#xff0c;这个wsgiref模块本身能够支持的并发量很少&#xff0c;最多1000左右&#xff0c;上线之后会换成uwsgi&#xff0c;并且还会加…

Redis 主库挂了,如何不间断服务?

目录 1、哨兵机制的基本流程 2、主观下线和客观下线 3、如何选定新的主库&#xff1f; 总结 // 你只管前行&#xff0c;剩下的交给时间 在 reids 主从库集群模式下&#xff0c;如果从库发生故障了&#xff0c;客户端可以继续向主库或其他从库发送请求&#xff0c;进行相关的…

宠物网站的技术 SEO:完整指南

您是宠物行业网站的从业者吗&#xff1f;那么您一定知道&#xff0c;当人们寻找与宠物相关的资源时&#xff0c;在搜索引擎结果中排名靠前有多么重要。 这就是技术SEO的用武之地&#xff01;它正在调整您网站的后端代码和服务器配置&#xff0c;以在 SERP 中排名更高。 在此&…

PCF8591多通道数据读取异常问题

问题描述 PCF8591在循环读取两个通道时&#xff0c;两个通道数据出现交错问题。 例如我们想实现&#xff1a;第一次读取通道一、第二次读取通道二、第三次读取通道一、第四次读取通道二……依次循环 但实际数据&#xff1a;第一次读取的值为0x80、第二次读取的值为通道一的值、…

西南科技大学C++程序设计实验二(类与对象一)

C++最大的特点就是面向对象,掌握它的几种基本性质还是好理解的,可以看我C++专栏的期末速成,希望对你们学习C++有帮助。 一、实验目的 1.理解简单类的定义、说明与使用 2.理解类中不同属性数据成员的访问特点 3.理解构造函数、析构函数的作用 重点:掌握类的定义与实现,…

java多线程-扩展知识一:进程线程、并发并行、同步异步

1、进程 进程&#xff08;Process&#xff09;是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配的基本单位&#xff0c;是操作系统结构的基础。在早期面向进程设计的计算机结构中&#xff0c;进程是程序的基本执行实体&#xff1b;在当代面向线程…

前端入门(三)Vue组件化编程、脚手架、插槽插件、存储、vuex、组件事件、动画、代理

文章目录 Vue 组件化编程 - .vue文件非单文件组件组件的注意点组件嵌套Vue实例对象和VueComponent实例对象Js对象原型与原型链Vue与VueComponent的重要内置关系 应用单文件组件构建 Vue脚手架 - vue.cli项目文件结构组件相关高级属性引用名 - ref数据接入 - props混入 - mixin …

轻巧高效的剃须好工具,DOCO黑刃电动剃须刀上手

剃须刀大家都用过&#xff0c;我比较喜欢电动剃须刀&#xff0c;尤其是多刀头的悬浮剃须刀&#xff0c;感觉用起来很方便&#xff0c;剃须效率也很高。最近我在用一款DOCO小蔻的黑刃电动剃须刀&#xff0c;这款剃须刀轻巧易用&#xff0c;而且性价比超高。 相比于同类产品&…

进程和线程的关系

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;JavaEE &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 进程&线程 1. 什么是进程PCB 2. 什么是…