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

您所在的位置:网站首页 vue3页面跳转 vue3实现动态菜单和动态路由和刷新后白屏处理

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

2023-03-16 02:12| 来源: 网络整理| 查看: 265

前言:

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

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

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

        动态菜单生成        动态添加路由

 

 

动态菜单生成 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)

在界面引用

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); }, }, }; 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, }); };

在界面引用

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 {//没有获得菜单数据 } }, }, }; 2.接收界面数据,实现动态界面 2.1界面实现

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

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

HomeMenu.vue 

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

//在组件中调用组件本身,使用递归的方法实现二级目录。

全部代码 

{{ item.menuName }} {{ item.menuName }} 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); } }, }, };

HomeAside.vue

{{ isCollapse ? "排班" : "智能排班系统" }} 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); }, }, }; .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; } } 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) });

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3