vue3实现动态菜单和动态路由和刷新后白屏处理

news2024/11/24 17:08:28

前言:

项目中,当每一个角色得到的界面不一致的时候,我们就不能使用静态菜单了,而是要从后端得到动态的菜单数据,然后动态的将菜单数据展示在界面上。

除了在界面展示,也还要将界面的路由动态添加,在路由动态添加之后,你可能会出现刷新界面,界面变白的情况,页面刷新白屏其实是因为vuex引起的,由于刷新页面vuex数据会丢失,所以动态添加路由这一步也就失效了。这种情况我会在最后给一个处理方法。

所以这个博客会做两个部分的处理:

  1.         动态菜单生成
  2.         动态添加路由

 

 

动态菜单生成

1.获得后端数据(有mock模拟数据,也可以使用后端接口)

1.1使用mock得到模拟数据

没有下载mock的可以查看:Vue项目中使用mockjs实现mock模拟数据 - ykCoder - 博客园 (cnblogs.com)

mock/modules/menu.js  保存模拟的后端数据

function list(res) {
  // res是一个请求对象,包含: url, type, body
  return {
    code: 200,
    message: "请求成功",
    //菜单数据,可以修改成你自己要的菜单
    data: [
      {
        id: "600d4075e218daaf4ec77e50",
        menuType: "1",
        menuName: "首页",
        path: "/Home",
        icon: "house",
      },
      {
        id: "600d4075e218daaf4ec77e51",
        menuType: "1",
        menuName: "公司管理",
        path: "/company",
        icon: "location",
        children: [
          {
            id: "600d525e602f452aaeeffcd9",
            menuType: "1",
            menuName: "公司资料",
            path: "/company/Company",
          },
          {
            id: "601bc4f8a794e23c2e42efa9",
            menuType: "1",
            menuName: "个人资料",
            path: "/company/Person",
          },
        ],
      },
  };
}

//暴露list
export default { list };

mock/index.js  引入mock/menu.js

// 引入mockjs
import Mock from 'mockjs'
// 引入模板函数类
import menu from './modules/menu'

// Mock函数
const { mock } = Mock

// 设置延时
Mock.setup({
  timeout: 400
})

// 使用拦截规则拦截命中的请求,mock(url, post/get, 返回的数据);
Mock.mock('/mock/menu', 'get', menu.list)

在界面引用

<script>
export default {
  data() {
    return {
      menuData:[],
    };
  },
  methods: {
    getMenu() {
      this.$http.get('/mock/menu').then((res) => {
        console.log(res)
        if (res.data.code === 200) {
          this.menuData = res.data.data;
          // console.log(this.menuData,"menuData")
          //获取菜单的数据,存入store中
          this.$store.commit("setMenu",this.menuData)
          //动态生成路由
          this.$store.commit("addMenu",this.$router)
        }
      })
    },
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
  },
};                   
</script>

1.2连接后端接口(统一接口管理),从后端得到菜单数据

如果对统一接口管理,有不明白的可以查看:

016-尚硅谷-尚品汇-API接口统一管理_哔哩哔哩_bilibili​​​​​​

Vue封装接口思路(包括请求(响应拦截器))_vue接口封装_忧郁火龙果的博客-CSDN博客

api/request.js

import axios from 'axios';

//1.利用axios对象的方法create,去创建一个axios实例。
const requests = axios.create({
    //配置对象
    //接口当中:路径都带有/api     基础路径,发送请求的时候,路径当中会出现api
    baseURL:"/api",
    //代表请求超时的时间
    timeout:5000,
})
//请求拦截器:
requests.interceptors.request.use((config) =>{
    //config:配置对象,对象里面有一个属性很重要,header请求头
    return config;
})

//响应拦截器
requests.interceptors.response.use((res)=>{
    //成功的回调函数:服务器相应数据回来以后,响应拦截器可以检测,可以做一些事情
    return res.data;
},(error)=>{
    //失败的回调函数
    return Promise.reject(new Error('faile'));
})

//对外暴露
export default requests;

api/menu.js

import requests from "./request";

export const menuList = (data) => {
  return requests({
    url: "/user/menus",
    method: 'GET',
    data: data,
  });
};

在界面引用

<script>
import { menuList } from "@/api/menu.js";

export default {
  data() {
    return {
      menuData:[],
    };
  },
  methods: {
    getMenu() {
      const id = 1;
      //假数据
      const res = menuList(id);
      console.log(res.data);
      if (res.code == 200) {//获得菜单导航数据
      this.menuData = res;
      } else {//没有获得菜单数据
      }
    },
  },
};                   
</script>

2.接收界面数据,实现动态界面

2.1界面实现

我的菜单界面是用两个vue文件写的。组件间的传值要用vuex,这里建议去看一下官网学习一下。

组件间的传值:Vue组件之间的传值 - 掘金 (juejin.cn)

HomeMenu.vue 

这里实现二级菜单用的是递归的方法,这个是我觉得很神奇的地方,我第一次在vue中使用到了递归。在此之前我觉得vue就只能实现做界面的功能,没有想到过vue也可以这么灵活。

<home-menu :menuData="item.children" />//在组件中调用组件本身,使用递归的方法实现二级目录。

全部代码 

<template>
  <div>
    <template v-for="item in menuData" :key="item">
      <el-sub-menu
        :index="item.id"
        v-if="
          item.children &&
          item.children.length > 0 &&
          item.children[0].menuType.toString() === '1'
        "
      >
        <template #title>
          <el-icon><component :is="item.icon" /></el-icon>
          <span>{{ item.menuName }}</span>
        </template>
        <home-menu :menuData="item.children" />
      </el-sub-menu>
      <el-menu-item
        @click="clickMenu(item)"
        v-else-if="item.menuType.toString() === '1'"
        :index="item.path"
        :key="item.id"
      >
        <template #title>
          <el-icon><component :is="item.icon" /></el-icon>
          <span>{{ item.menuName }}</span>
        </template>
      </el-menu-item>
    </template>
  </div>
</template>
<script>
export default {
  name: "home-menu",
  //为了实现组件间的传值
  props: ["menuData"],
  methods: {
    //点击菜单
    clickMenu(item) {
      console.log("item:" + item);
      //当前路由与跳转路由不一致时跳转
      if (this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) {
        this.$router.push(item.path);
      }
    },
  },
};
</script>

HomeAside.vue

<template>
  <div>
    <el-menu
      active-text-color="#ffd04b"
      background-color="#545c64"
      class="el-menu-vertical-demo"
      default-active="2"
      text-color="#fff"
      @open="handleOpen"
      @close="handleClose"
    >
      <h3>{{ isCollapse ? "排班" : "智能排班系统" }}</h3>
      <home-menu :menuData="menuData"></home-menu>
    </el-menu>
  </div>
</template>
<script>
import HomeMenu from "@/components/menu/HomeMenu.vue";
import { thisTypeAnnotation } from "@babel/types";
import { mapState } from 'vuex';

export default {
  components: {
    "home-menu": HomeMenu,
  },
  data() {
    return {
      menuData:[],
    };
  },
  mounted() {
    //获得菜单
    this.getMenu();
  },
  computed: {
    //给store传递menuData的值
    ...mapState({
      menuData: (state) => state.menu.menuData
    }),
  },
  methods: {
    getMenu() {
      this.$http.get('/mock/menu').then((res) => {
        console.log(res)
        if (res.data.code === 200) {
          this.menuData = res.data.data;
          // console.log(this.menuData,"menuData")
          //获取菜单的数据,存入store中
          this.$store.commit("setMenu",this.menuData)
          //动态生成路由
          this.$store.commit("addMenu",this.$router)
        }
      })
    },
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
  },
};                   
</script>

<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu {
  height: 100vh;
  border-right: none;
  h3 {
    color: #fff;
    text-align: center;
    line-height: 48px;
    font-size: 16px;
    font-weight: 400px;
  }
}
</style>

2.2组价间传值的使用

store/menu.js  这里的addMenu函数值实现动态路由的关键,在下面会有分析

export default {
  state: {
    // 动态菜单
    menuData: [],
  },
  //修改字段
  mutations: {
    //设置菜单的数据
    setMenu(state, val) {
      state.menuData = val;
    },
    //动态注册路由
    addMenu(state, router) {
      // 处理动态路由的数据
      const menuData = JSON.parse(JSON.stringify(state.menuData));
      const menuArray = [];
      menuData.forEach((item) => {
        if (item.children && item.children.length >= 1) {
          menuArray.push(...item.children);
        } else {
            menuArray.push(item);
        }
      });
      console.log(menuArray, "menuArray");
      // 路由的动态添加
      if (menuArray[0] !== "") {
        menuArray.forEach((item) => {
          router.addRoute("main", { path: `${item.path}`,component: () => import(`@/views${item.path}.vue`) });
        });
      }
    },
  },
};

menu/index.js

import { createStore } from 'vuex'
import createPersistedState from "vuex-persistedstate"
import menu from './menu'


export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    menu
  },
  /* vuex数据持久化配置 */
  plugins: [
    createPersistedState({
      // 存储方式:localStorage、sessionStorage、cookies
      storage: window.sessionStorage,
      // 存储的 key 的key值
      key: "store",
      reducer(state) { //render错误修改
        // 要存储的数据:本项目采用es6扩展运算符的方式存储了state中所有的数据
        return { ...state };
      }
    })
  ]
})

3.实现动态路由

3.1实现动态路由的代码分析

上面的代码已经实现了动态路由,这里是解释一下动态路由的关键上面,网上关于动态路由的代码很多,但是对于第一次做动态路由的人来说,想要去看懂事有点难度的。

创建一个新的空数组,遍历menuData的时候根据元素有没有children来分别处理,将需要的数据保存到新数组中,通过传入的path路径来添加路由。

最重要的部分来了,vue router4的版本不再使用router.addRoutes而是router.addRoute,这个地方建议看官方文档

动态路由 | Vue Router (vuejs.org)

 router.addRoute("main", { path: `${item.path}`,component: () => import(`@/views${item.path}.vue`) });
//动态注册路由
    addMenu(state, router) {
      // 处理动态路由的数据
      const menuData = JSON.parse(JSON.stringify(state.menuData));
      const menuArray = [];
      menuData.forEach((item) => {
        if (item.children && item.children.length >= 1) {
          menuArray.push(...item.children);
        } else {
            menuArray.push(item);
        }
      });
      console.log(menuArray, "menuArray");
      // 路由的动态添加
      if (menuArray[0] !== "") {
        menuArray.forEach((item) => {
          router.addRoute("main", { path: `${item.path}`,component: () => import(`@/views${item.path}.vue`) });
        });
      }
    },

下面贴一下vue2项目的写法,这里我使用时先把元素中添加component属性,这里和上面的写法有点不一样,但是我建议还是用上面的好一点,这个写法可能会出bug。

item.component =  (resolve) => require([`@/views/home/${item.url}`], resolve)
//动态注册路由
        addMenu(state, router) {
            // 处理动态路由的数据
            const menuArray = []
            state.menuData.forEach(item => {
                if (item.children) {
                    item.children = item.children.map(item => {
                        item.component = (resolve) => require([`@/views/home/${item.url}`], resolve)
                        return item
                    })
                    menuArray.push(...item.children)
                } else {
                    item.component =  (resolve) => require([`@/views/home/${item.url}`], resolve)
                    menuArray.push(item)
                }
            })
            console.log(menuArray, 'menuArray')
            // 路由的动态添加
            menuArray.forEach(item => {
                router.addRoute('main', item)
            })
        },

还有一个处理方法,你可以在传过来的数据中就传path,component的值,最后直接使用

router.addRoute() 调用就行了。

在HomeAside.vue界面之间的引用

//获取菜单的数据,存入store中
this.$store.commit("setMenu",this.menuData)
//动态生成路由
this.$store.commit("addMenu",this.$router)

3.2刷新界面后,白屏处理

恭喜你,来到最后一步,在最前面的时候说过,白屏问题是因为vuex引起的,由于刷新页面vuex数据会丢失,所以动态添加路由这一步也就失效了。我在网上找了很多方法都没有解决,最后回归到问题的本质,刷新界面vuex的数据会丢失,那么我们不让数据丢失不就行了,我的处理方法是在

mian.ts中再保存一遍路由的数据。

这个是vue3的处理:

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";

import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import * as ElIconModules from "@element-plus/icons";

import '@/mock';
import axios from 'axios';
import VueAxios from 'vue-axios';

//动态菜单路由的生成
const addMenu = () => {
  store.commit("addMenu",router)
}
addMenu()

const app = createApp(App);
app.use(store).use(router).use(ElementPlus).use(VueAxios,axios).mount("#app");

// 统一注册Icon图标
for (const iconName in ElIconModules) {
  if (Reflect.has(ElIconModules, iconName)) {
    const item = ElIconModules[iconName];
    app.component(iconName, item);
  }
}

这个是vue2的处理:

import Vue from 'vue';
import router from './router'
import store from './store'
import App from './App.vue'


import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI);

import axios from 'axios'
//配置请求根路径
axios.defaults.baseURL = "http://localhost:8088"                                 
//将axios作为全局的自定义属性,每个组件可以在内部组件访问
Vue.prototype.$http = axios

//添加全局前置导航守卫
router.beforeEach((to, from, next) => {
  //判断token是否存在
  // localStorage.setItem("token", message.data.token);
  const token = localStorage.getItem("token");
  // localStorage.clear();
  console.log(token,'token')
  if( !token && to.name !== 'login' ){//token不存在,没有登录
      next({ name : 'login' })
  } else {
    next();
  }
})

new Vue({
  router,
  store,
  el: '#app',
  created() {
    store.commit('addMenu',router)
  },
  render: h => h(App)
});

好了,最后希望大家都能看懂,如果有什么问题可以提出来。

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

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

相关文章

档案管理流程,文件上传,文件解析流程

文件上传 先简单说下文件上传的步骤吧 控制层简单左下转发 PostMapping("uploadAndParsing") public ResponseResult uploadAndParsing(RequestParam("file") MultipartFile file){try {String accessToken Header.getAuthorization(request.getHeader(…

2022年度互联网平均薪资出炉~

近期&#xff0c;国家统计局发布2022年平均工资数据&#xff0c;互联网行业薪资再次成为大家关注的焦点。 在2022年分行业门类分岗位就业人员年平均工资中&#xff0c;信息传输、软件和信息技术服务业的薪资遥遥领先其他行业&#xff0c;为全国平均薪资水平的 1.78 倍&#xf…

H-buildX项目(学习笔记1.0)

记录一下自己的学习过程。 首先&#xff0c;下载H-buildX软件&#xff0c;直接度娘下载即可。 以前一直用的是vscode&#xff0c;这次做网上的项目用到了这个。 打开 就是这样的界面&#xff0c;首先介绍几个常用的功能 右上角的预览&#xff0c;也就是运行你的Vue项目的界面效…

2023华为OD机试(A卷+B卷)(Java C++ Python JS)真题目录 + 考点 + 通过率

文章目录 &#x1f680;前言华为OD刷题列表&#xff0c;每天十题&#xff0c;刷出算法新高度&#xff0c;刷出人生新际遇&#x1f530;刷题列表&#xff1a;华为OD机试真题&#xff08;Java C Python JS&#xff09; &#x1f680;其他华为OD机试题清单 &#x1f4dd;最后作者&…

【最新整理】一起看看86 个 ChatGPT 插件

今天我们来看看这86个插件都是做什么的&#xff1f; Shimmer&#xff1a;跟踪膳食并获得更健康生活方式的见解 World News&#xff1a;总结新闻头条 Bohita&#xff1a;用您能描述的任何图像制作服装 Portfolio Pilot&#xff1a;您的 AI 投资指南&#xff1a;投资组合评估…

跟着我学 AI丨五分钟了解人工智能的发展史

随着 ChatGPT 火出圈&#xff0c;又接二连三出现了文心一言、Midjourney、FireFly 等创新性的 AI 产品&#xff0c;互联网掀起的 AI 风暴已经席卷了全球各个角落。AI 究竟为什么这么强大&#xff1f;从什么时候开始冒出来的呢&#xff1f;今天我就带大家认识一下 AI 的发展史。…

软件设计师考试——面向对象设计模式分类

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、面向对象设计——设计模式的分类一、创建型模式二、结构型模式三、行为型模式 二、历年真题——面向对象设计模式分类2009上半年2009下半年2010上半年2010下半年…

深度学习神经网络学习笔记-多模态方向-12-DBpedia: A Nucleus for a Web of Open Data

摘要 DBpedia是一个社区努力从维基百科中提取结构化信息&#xff0c;并使这些信息在网络上可用。DBpedia允许您对来自维基百科的数据集提出复杂的查询&#xff0c;并将网络上的其他数据集链接到维基百科数据。我们描述了DBpedia数据集的提取&#xff0c;以及产生的信息如何在网…

img标签-访问绝对路径图片出现403的解决办法

img标签-访问绝对路径图片出现403的解决办法 图片请求显示403打不开 403是防止盗链的错误&#xff08;这种设计&#xff0c;是api厂商正常保证自己的服务器不被刷流量&#xff09; 方法一&#xff1a;使用no-referrer 这种方案不仅针对图片的防盗链,还可以是其他标签. 在前端…

「谷云科技」RestCloud新一代(智能)全域数据集成平台发布

5月18日&#xff0c;RestCloud在其成立六周年的当天&#xff0c;发布了“新一代&#xff08;智能&#xff09;全域数据集成平台”。 根据业内专家、学者和从业者通过实践和研究总结&#xff0c;数据集成大体可以分为4个阶段&#xff1a;早期阶段、数据集成软件工具、企业级数据…

chat gpt 中国镜像网站

随着人工智能技术的快速发展&#xff0c;自然语言处理对人们日常生活和工作中的应用变得越来越普及&#xff0c;其中机器翻译、自动问答、智能客服和语音识别等技术尤其受到欢迎。Chat GPT模型是其中一种被广泛使用的技术&#xff0c;由OpenAI开发&#xff0c;使用了Transforme…

【TCP】状态转换

TCP状态转换 这个图N多人都知道&#xff0c;它排除和定位网络或系统故障时大有帮助&#xff0c;但是怎样牢牢地将这张图刻在脑中呢&#xff1f;那么你就一定要对这张图的每一个状态&#xff0c;及转换的过程有深刻的认识&#xff0c;不能只停留在一知半解之中。下面对这张图的…

Blender 建模风扇(UV贴图、图像纹理、环境纹理、伽玛、Cycles渲染)

目录 1. 风扇建模1.1 风扇外壳1.2 风扇内壳1.3 前盖1.4 后盖1.5 风扇叶1.6 扇叶连接部分1.7 其他细节 2. UV、材质、渲染2.1 材质属性&#xff1a;图像纹理2.2 UV贴图2.3 基础材质2.4 伽玛值2.5 世界属性&#xff1a;环境纹理2.6 背景平面2.7 灯光、摄像机2.8 渲染属性2.9 渲染…

COCO-Annotator安装和使用 [非常详细]

COCO-Annotator安装和使用 1.所需环境 安装COCO-Annotator前需要先安装Docker&#xff0c;有需要可以参考这篇文章&#xff1a;Windows下安装docker 2.安装 在要安装的目录下进入cmd输入命令 git clone https://github.com/jsbroks/coco-annotatorcd命令进入文件夹 cd co…

DICOM通信协议标准解析

DICOM通信协议的特点和内容概述 DICOM标准的主要特点 DICOM标准的主要特点 是一种上层网络协议 只有在建立“关联”之后&#xff0c;才能进行DICOM命令和数据的发送和接收。 DICOM编码的特点 标准定义了26中内部数据类型像素数据的编码支持JPEG的图像压缩图像可以包含缩略…

《元宇宙之声》:Meta MCDH

为下一代建造未来就绪的校园。 在本期节目中&#xff0c;我们访问了香港路德会马锦明慈善基金马陈端喜纪念中学&#xff08;MCDH&#xff09;的陈婉玲校长&#xff0c;讨论了 MCDH 改革教育的愿景&#xff0c;通过培养年轻的创作者&#xff0c;让他们迈出进入 The Sandbox 的第…

【谷粒商城之秒杀服务】

本笔记内容为尚硅谷谷粒商城秒杀服务部分 目录 一、秒杀业务的介绍 秒杀设计 秒杀流程 二、搭建秒杀服务环境 1、秒杀服务后台管理系统 2、搭建秒杀服务环境 二、定时任务 1、cron 表达式 2、测试 三、商品上架 1、远程查询秒杀的活动以及关联的商品信息 2、在R…

「一本通 1.2 练习 3」灯泡

题目描述 相比 w i l d l e o p a r d wildleopard wildleopard 的家&#xff0c;他的弟弟 m i l d l e o p a r d mildleopard mildleopard 比较穷。他的房子是狭窄的而且在他的房间里面仅有一个灯泡。每天晚上&#xff0c;他徘徊在自己狭小的房子里&#xff0c;思考如何赚…

探索Kotlin 1.8.20新特性

探索Kotlin 1.8.20新特性 Kotlin 1.8.20已经发布&#xff0c;我们将探索一些新功能和改进。 我们将仅涵盖新的语言功能和标准库更新。请参考参考资料部分以了解此版本的完整详情。 语言更新 Enum 类entries函数 为什么需要这个函数&#xff1f; values() - 返回数组&#x…

电商数仓建模案例

目录 一、数据仓库分层规划二、数据仓库构建流程2.1 数据调研2.2 明确数据域2.3 构建业务总线矩阵2.4 明确统计指标2.4 维度模型设计2.5 汇总模型设计 三、数仓建模之ODS层3.1 日志表3.1.1 日志表表结构 3.2 业务表3.2.1 业务表表结构 四、数仓开发之DIM层4.1 商品维度表4.2 优…