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


总结

递归处理后端响应的菜单树,后依次通过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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值