Vue + Element UI implements permission management system front-end (10): dynamic loading menu
Dynamically loading menu
In the past, our navigation tree was hard-coded in the page, but in actual applications, it was dynamically generated after obtaining the menu data from the backend server.
Here we will use the data format prepared in the previous article to mock out the simulation data, and then dynamically generate our navigation menu.
Interface modularization
We have always paid attention to modularization. Previously, the interfaces were concentrated in interface.js. We now renamed it api.js, and transferred all the original login, user, and menu-related interfaces to our new interface module file.
The file structure after modularization is as shown below
After modularization, the module interface is written in the corresponding module interface file, as shown below is the login module
login.js
import axios from '../axios' /* * System login module */ // Log in export const login = data => { return axios({ url: '/login', method: 'post', data }) } // Sign out export const logout = () => { return axios({ url: '/logout', method: 'get' }) }
After modularization, the parent module can be imported like this
api.js
/* * Interface unified integration module */ import * as login from './moudules/login' import * as user from './moudules/user' import * as menu from './moudules/menu' //Export all by default export default { login, user, menu }
Because we are exporting the parent module here, when calling the specific interface, we also need to add the module on the original basis, like this.
As in api.js above, we exported the entire login file, and there are multiple methods such as login and logout under the login file.
Navigation menu tree interface
We create an interface to query the navigation menu tree under menu.js.
import axios from '../axios' /* * Menu management module */ export const findMenuTree = () => { return axios({ url: '/menu/findTree', method: 'get' }) }
If it is not introduced in api.js, remember to introduce it.
Page interface call
The interface is already there. In the navigation menu component MenuBar.vue, we load the menu and store it in the store.
Page menu rendering
Still in MenuBar.vue, the page reads store data through the encapsulated menu tree component and generates menus recursively.
Create a new menu tree component, generate a menu recursively, and jump to the specified route according to the menu URL in the click response function.
components/MenuTree/index.js
<template> <el-submenu v-if="menu.children && menu.children.length >= 1" :index="menu.menuId + ''"> <template slot="title"> <i :class="menu.icon"></i> <span slot="title">{ {menu.name}}</span> </template> <MenuTree v-for="item in menu.children" :key="item.menuId" :menu="item"></MenuTree> </el-submenu> <el-menu-item v-else :index="menu.menuId + ''" @click="handleRoute(menu)"> <i :class="menu.icon"></i> <span slot="title">{ {menu.name}}</span> </el-menu-item> </template> <script> export default { name: 'MenuTree', props: { menu: { type: Object, required: true } }, methods: { handleRoute (menu) { // Jump to the specified route through the menu URL this.$router.push(menu.url) } } } </script>
Provide Mock data
The interface is in place, and the page calling and rendering have been written. It’s time to provide Mock data.
There is too much data corresponding to the mock findTree interface in mock/modules/menu.js, so I won’t post it here.
export function findTree() { return { url: 'http://localhost:8080/menu/findTree', type: 'get', data: menuTreeData // json object data } }
Test effect
After the startup is completed and we enter the homepage, we see that the navigation menu has been successfully loaded, oh yeah!
However, we happily clicked on the menu and found that this was the case, oh no!
There is nothing wrong with it, but obviously, you are smart and have seen through everything. We only provided a route called /user before, and did not provide a route for /sys/user.
Okay, let's modify it a little, open the routing configuration, and try changing /user to /sys/user.
Sure enough, after modification, you can jump to the user interface normally.
But that’s not right. The routing configuration here is hard-coded. The navigation menu is dynamically generated from the menu data. This routing configuration should also be dynamically added based on the menu data. Well, so next we will discuss the issue of dynamic routing configuration.
Dynamic routing implementation
AddRoutes is provided in vue's route to implement dynamic routing. Open MenuBar.vue, and we add dynamic routing configuration while loading the navigation menu.
MenuBar.vue
Among them, addDynamicMenuRoutes is the key code to return dynamic routing configuration according to the menu.
addDynamicMenuRoutes method details:
/** * Add dynamic (menu) routing * @param {*} menuList menu list * @param {*} routes dynamic (menu) routes created recursively */ addDynamicMenuRoutes (menuList = [], routes = []) { var temp = [] for (var i = 0; i < menuList.length; i++) { if (menuList[i].children && menuList[i].children.length >= 1) { temp = temp.concat(menuList[i].children) } else if (menuList[i].url && /\S/.test(menuList[i].url)) { menuList[i].url = menuList[i].url.replace(/^\//, '') //Create routing configuration var route = { path: menuList[i].url, component: null, name: menuList[i].name, meta: { menuId: menuList[i].menuId, title: menuList[i].name, isDynamic: true, isTab: true, iframeUrl: '' } } // The url starts with http[s]:// and is displayed through iframe if (isURL(menuList[i].url)) { route['path'] = menuList[i].url route['name'] = menuList[i].name route['meta']['iframeUrl'] = menuList[i].url } else { try { // Dynamically load the vue component according to the menu URL. It is required that the vue component must be stored according to the url path. // If url="sys/user", the component path should be "@/views/sys/user.vue", otherwise the component cannot be loaded. let array = menuList[i].url.split('/') let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1) route['component'] = resolve => require([`@/views/${url}`], resolve) } catch (e) {} } routes.push(route) } } if (temp.length >= 1) { this.addDynamicMenuRoutes(temp, routes) } else { console.log(routes) } return routes }
The component structure of the dynamic menu page is slightly adjusted. It needs to match the menu URL so that the component path can be determined based on the menu URL to dynamically load the component.
Clean up the routing file, dispose of the routing configuration related to the dynamic menu, and leave some fixed global routes.
Dynamic routing test
After the startup is completed, enter the home page, click User Management, and be routed to the user management page.
Click Organization Management to be directed to the organization management page.
Okay, the dynamic routing function has been implemented here, give yourself a round of applause.
Big hole appears when page refreshes
Previously, we placed the loading of the navigation menu and routing in the menu bar page MenuBar.vue. All display and routing were normal, and there seemed to be no problem. However, when we refresh the page based on the path, the problem occurs.
As shown in the figure below, when we click to refresh the browser on the user management page, it will be completely white. This is because refreshing the browser will cause the entire vue to be reloaded, the routing will be re-initialized, and later in Menu.bar The added dynamic route is gone, so no matching route is found when jumping, and the page that jumps is a page that does not exist, so it is completely blank.
Professional pit filling guide
This is obviously because the loading timing of the dynamic menu and routing is wrong. How to solve this problem? Since the problem lies in the loading timing, just find a place that can trigger reloading when the page is refreshed.
There are many such places, such as the hook function in the Vue loading process, the route navigation guard function, etc. Here we choose to load it in the beforeEach function of the route navigation guard to ensure that every time the route jumps, there will be dynamics. Menus and routing.
Transfer the code that originally loaded the dynamic menu and routing in MenuBar.vue to the routing configuration router/index.
beforeEach:
router.beforeEach((to, from, next) => { // After successful login on the login interface, the user information will be saved in the session //The existence time is the session life cycle, and it will expire when the page is closed. let isLogin = sessionStorage.getItem('user') if (to.path === '/login') { // If you are accessing the login interface, if the user session information exists, it means you have logged in and jump to the home page. if(isLogin) { next({ path: '/' }) } else { next() } } else { // If you access the non-login interface and the user session information does not exist, it means you are not logged in and jump to the login interface. if (!isLogin) { next({ path: '/login' }) } else { //Load dynamic menu and routing addDynamicMenuAndRoutes() next() } } })
addDynamicMenuAndRoutes:
/** * Load dynamic menus and routing */ function addDynamicMenuAndRoutes() { api.menu.findMenuTree() .then( (res) => { store.commit('setMenuTree', res.data) //Add dynamic route let dynamicRoutes = addDynamicRoutes(res.data) router.options.routes[0].children = router.options.routes[0].children.concat(dynamicRoutes) router.addRoutes(router.options.routes); }) .catch(function(res) { alert(res); }); }
addDynamicRoutes:
/** * Add dynamic (menu) routing * @param {*} menuList menu list * @param {*} routes dynamic (menu) routes created recursively */ function addDynamicRoutes (menuList = [], routes = []) { var temp = [] for (var i = 0; i < menuList.length; i++) { if (menuList[i].children && menuList[i].children.length >= 1) { temp = temp.concat(menuList[i].children) } else if (menuList[i].url && /\S/.test(menuList[i].url)) { menuList[i].url = menuList[i].url.replace(/^\//, '') //Create routing configuration var route = { path: menuList[i].url, component: null, name: menuList[i].name, meta: { menuId: menuList[i].menuId, title: menuList[i].name, isDynamic: true, isTab: true, iframeUrl: '' } } // The url starts with http[s]:// and is displayed through iframe if (isURL(menuList[i].url)) { route['path'] = menuList[i].url route['name'] = menuList[i].name route['meta']['iframeUrl'] = menuList[i].url } else { try { // Dynamically load the vue component according to the menu URL. It is required that the vue component must be stored according to the url path. // If url="sys/user", the component path should be "@/views/sys/user.vue", otherwise the component cannot be loaded. let array = menuList[i].url.split('/') let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1) route['component'] = resolve => require([`@/views/${url}`], resolve) } catch (e) {} } routes.push(route) } } if (temp.length >= 1) { addDynamicRoutes(temp, routes) } else { console.log(routes) } return routes }
Of course, don’t forget to introduce a few things you need to use and clean up the code of the navigation menu bar.
Test effect
After the startup is completed, go to the home page, click User Management, and click the refresh button.
After refreshing, the menu is closed, but the page still stays on the user management page correctly. Mom no longer has to worry about me refreshing!
Save loading status
Now every time the route jumps, the menu data will be re-obtained to generate the menu and route. Even if the page is not refreshed, it will be obtained again, which will affect the performance. Let's improve it by saving the status to the store after successful loading. Check the loading status of the store before each load, so as to avoid repeated loading in the absence of page refresh.
Add menu routing loading status in the store to avoid repeated loading of the page without refreshing.
Modify the routing configuration, determine the loading status before loading, load only if it is not loaded, and save the loading status after loading.
solve a problem
When routing jumps, the routing seems to be superimposed on the original path and the routing path jumps.
If the path is http://localhost:8090/#/sys/dept, click User Management.
The code corresponds to this.$router.push(‘’sys/user), and the route is earned http://localhost:8090/#/sys/sys/user.
There is one more sys than the correct route. The reason is not yet known.
Currently, I jump back to the main page first and then do the real jump before actually jumping.
This problem can be solved, but it is not good to have an extra jump for no reason. The solution is in progress. . .