Note: Vue-router does not have full control over front-end routing permissions.

1. Implementation ideas

Add routing rules dynamically using the vue-Router instance function addRoutes.

2. Implementation steps

2.1 route matching judgment

// src/router.js

import Vue from 'vue';
import Store from '@/store';
import Router from 'vue-router';
import Cookie from 'js-cookie';
const routers = new Router({
	base : "/test".// Define default routes such as login, 404, 401, etc
    routes : [{
    	path : "/ 404".// ...}, {path : "/ 401".// ...}]})/ /... Omit some code
routes.beforeEach((to, from, next) = > {
	const { meta, matched, path } = to;
    let isMatched = matched && matched.length > 0; // Whether to match the route
    if(isMatched){
    
    }else{}})Copy the code

Matching results are simply achieved by using the parameter to in vue-router front-guard beforeEach

2.2 Login access control

In the actual development of routes, there are often logon access and logon access situations, so you can define isAuth fields in the token and route configuration meta information to distinguish them.

/ /... Omit some duplicate code

const openRouters = [];
const authRouters = [{
	path : "order/list".// ...
    meta : {
    	// Whether to authenticate (the default is false or true is up to the developer)
    	isAuth : true}}]; routes.beforeEach((to, from, next) = > {
	const { meta, matched, path } = to;
    let isMatched = matched && matched.length > 0; // Whether to match the route
    let isLogin = Cookie.get("token") | |null;
    let { isAuth } = (meta || {});
    if(isMatched){
    	// The route is matched
    	if(isAuth){
        	// Login access is required
            if(isLogin){
            	// Access logged in
                next(); // Call the hook function
            }else{
            	// No login access
            	next("/login"); // Jump to login}}else{
        	// No login access is required
        	next(); // Call the hook function}}else{
    	// No route was matched
        if(isLogin){
        	// Access logged in
            
        }else{
        	// No login access
        	next("/login"); // Jump to login}}})Copy the code

2.3. Dynamically Adding routing rules

To dynamically add routing rules, you only need to use the vue-router instance router.addRoutes(Routes: Array).

So how do we get the routing rules that need to be added dynamically?

2.4. Build a routing rule matching function

Suppose the routing permission list obtained by the background looks like this:

[{
  resourceUrl : "/order/list".childMenu:... }]Copy the code

In order to compare user permissions and routes we need to extract the permission route array

// Simply get all permission urls recursively
export function getAuthRouters(authMenu) {
    let authRouters = [];
    (authMenu || []).forEach((item) = > {
        const { resourceUrl, childMenu } = item;
        resourceUrl && authRouters.push(resourceUrl);
        if (childMenu && childMenu.length > 0) {
            // Merge submenusauthRouters = [...authRouters, ...getAuthRouters(childMenu)]; }});return authRouters;
}
Copy the code

GetAuthRouters = getAuthRouters = getAuthRouters = getAuthRouters

This is associated with (I’m using the RBAC model here) system configuration permissions. Vue-router The routing rule must be consistent with the permission configuration. Therefore, vue-Router routing rules are dynamically combined recursively to compare the routing permissions of users. If it matches, the route is reserved. Then, a vue-Router routing rule configuration is obtained after filtering. Finally, the routing rules are added through the instance method addRoutes. The specific implementation code is as follows:

// src/utils/index.js
const { pathToRegexp } = require('path-to-regexp');

export function createAuthRouters(authRouters) {
    const isAuthUrl = (url) = > {
        return (authRouters || []).some((cUrl) = > {
            return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString();
        });
    };
    return function createRouters(routers, upperPath) {
        let nRouters = [];
        (routers || []).forEach((item) = > {
            const { children, path, name } = item;
            let isMatched = false, nItem = { ... item }, fullPath =`${upperPath || ' '}/${path}`.replace(/ / / {2}.'/'),
                nChildren = null;
            children && (nChildren = createRouters(children, fullPath));
            // 1. The current route matches
            if (isAuthUrl(fullPath)) {
                isMatched = true;
            }
            // 2. Child routes exist
            if (nChildren && nChildren.length > 0) {
                nItem.children = nChildren;
                isMatched = true;
            }
            // Special processing (no need to delete)
            if(name === "home"){
                isMatched = true;
            }
            // nItem
            isMatched && nRouters.push(nItem);
        });
        return nRouters;
    };
}
Copy the code

It is worth noting that the createAuthRouters method controls whether the reservation is made through the variable isMatched, which is determined by the variable because the parent route in nested routing may not match, but the child route can match, so the parent route rule also needs the child route to participate in whether the reservation is made. Such as:

// Routing rules
const routers = new Router({
	base : "/test".// Define default routes such as login, 404, 401, etc
    routes : [{
    	path : "/".children : [{
        	path : "login". }, {path : "about". }, {path : "order".children : [{
            	path : "id"}}}}]]])// User permissions
["/order/id"]; // "/" is not equal to "/order/id", "/" is not equal to "/order", but the child path "/order/id" == "/order/id", so not only keep path: "/", but also keep path: "Order" nesting layer.

Copy the code

2.5. Dynamic registration



/ /... Omit some duplicate code

const openRouters = [];
const authRouters = [{
	path : "order/list".// ...
    meta : {
    	// Whether to authenticate (the default is false or true is up to the developer)
    	isAuth : true}}];/* Dynamically register routes */
async function AddRoutes() {
	// Obtain the user route permission
    let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU);
    try {
        const { code, data } = res || {};
        if (code === '000') {
            let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base);
            // Register the route
            routes.addRoutes([].concat(newAuthRoutes, openRouters));
            // The Settings are registered
            Store.commit('UPDATE_IS_ADD_ROUTERS'.true);
            // Save the menu information
            Store.commit('UPDATE_MENU_INFO', data); }}catch (error) {
        console.error('>>> AddRoutes() - error:', error);
    }
}

routes.beforeEach((to, from, next) = > {
	const { meta, matched, path } = to;
    let isMatched = matched && matched.length > 0; // Whether to match the route
    let isLogin = Cookie.get("token") | |null;
    let { isAuth } = (meta || {});
    if(isMatched){
    	// The route is matched
    	if(isAuth){
        	// Login access is required
            if(isLogin){
            	// Access logged in
                next(); // Call the hook function
            }else{
            	// No login access
            	next("/login"); // Jump to login}}else{
        	// No login access is required
        	next(); // Call the hook function}}else{
    	// No route was matched
        if(isLogin){
        	// Access logged in
            AddRoutes();
            next();
        }else{
        	// No login access
        	next("/login"); // Jump to login}}})Copy the code

2.6. Classification and arrangement

/* Route prefixes */
let { origin } = window.location || {};
routes.beforeEach((to, from, next) = > {
    const { meta, matched, path } = to;
    let isMatched = matched && matched.length > 0; // Whether it matches
    let isAuth = (meta || {}).isAuth; // Whether to authorize access
    let { isAddRoutes } = Store.state; // Register the route
    let isLogin = Cookie.get('token') | |null; // Whether to log in
    if((isMatched && ! isAuth) || (isMatched && isAuth && isLogin)) {// next()
        // 1. Match the route && unlogged access
        // 2. Match route && login access && login
        next();
    } else if((isMatched && isAuth && ! isLogin) || (! isMatched && ! isLogin)) {/ / login
        // 1. Match the route && login access && Not logged in
        // 2. The route is not matched && is not logged in
        next(`/login? r=${origin}/e-lottery${path}`);
    } else if(! isMatched && isLogin && isAddRoutes) {/ / 404
        // 1. Unmatched route && Log in && dynamically register a route
        next('/ 404');
    } else if(! isMatched && isLogin && ! isAddRoutes) {// Register the route
        // 1. No route is matched && Login && The route is not dynamically registeredAddRoutes(); next(); }});Copy the code

Well! It looks so much better.

3. Complete implementation code

// src/utils/index.js
const { pathToRegexp } = require('path-to-regexp');

export function getAuthRouters(authMenu) {
    let authRouters = [];
    (authMenu || []).forEach((item) = > {
        const { resourceUrl, childMenu } = item;
        resourceUrl && authRouters.push(resourceUrl);
        if (childMenu && childMenu.length > 0) {
            // Merge submenusauthRouters = [...authRouters, ...getAuthRouters(childMenu)]; }});return authRouters;
}
/ * * * *@param { Array } authRouters* /
export function createAuthRouters(authRouters) {
    const isAuthUrl = (url) = > {
        return (authRouters || []).some((cUrl) = > {
            return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString();
        });
    };
    return function createRouters(routers, upperPath) {
        let nRouters = [];
        (routers || []).forEach((item) = > {
            const { children, path, name } = item;
            let isMatched = false, nItem = { ... item }, fullPath =`${upperPath || ' '}/${path}`.replace(/ / / {2}.'/'),
                nChildren = null;
            children && (nChildren = createRouters(children, fullPath));
            // 1. The current route matches
            if (isAuthUrl(fullPath)) {
                isMatched = true;
            }
            // 2. Child routes exist
            if (nChildren && nChildren.length > 0) {
                nItem.children = nChildren;
                isMatched = true;
            }
            // Special processing
            if(name === "home"){
                isMatched = true;
            }
            // nItem
            isMatched && nRouters.push(nItem);
        });
        return nRouters;
    };
}

// src/router.js

import Vue from 'vue';
import Store from '@/store';
import Router from 'vue-router';
import Cookie from 'js-cookie';

const openRouters = [];
const authRouters = [{
	path : "order/list".// ...
    meta : {
    	// Whether to authenticate (the default is false or true is up to the developer)
    	isAuth : true}}];/* Dynamically register routes */
async function AddRoutes() {
	// Obtain the user route permission
    let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU);
    try {
        const { code, data } = res || {};
        if (code === '000') {
            let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base);
            // Register the route
            routes.addRoutes([].concat(newAuthRoutes, openRouters));
            // The Settings are registered
            Store.commit('UPDATE_IS_ADD_ROUTERS'.true);
            // Save the menu information
            Store.commit('UPDATE_MENU_INFO', data); }}catch (error) {
        console.error('>>> AddRoutes() - error:', error); }}/* Route prefixes */
let { origin } = window.location || {};
routes.beforeEach((to, from, next) = > {
    const { meta, matched, path } = to;
    let isMatched = matched && matched.length > 0; // Whether it matches
    let isAuth = (meta || {}).isAuth; // Whether to authorize access
    let { isAddRoutes } = Store.state; // Register the route
    let isLogin = Cookie.get('token') | |null; // Whether to log in
    if((isMatched && ! isAuth) || (isMatched && isAuth && isLogin)) {// next()
        // 1. Match the route && unlogged access
        // 2. Match route && login access && login
        next();
    } else if((isMatched && isAuth && ! isLogin) || (! isMatched && ! isLogin)) {/ / login
        // 1. Match the route && login access && Not logged in
        // 2. The route is not matched && is not logged in
        next(`/login? r=${origin}/e-lottery${path}`);
    } else if(! isMatched && isLogin && isAddRoutes) {/ / 404
        // 1. Unmatched route && Log in && dynamically register a route
        next('/ 404');
    } else if(! isMatched && isLogin && ! isAddRoutes) {// Register the route
        // 1. No route is matched && Login && The route is not dynamically registeredAddRoutes(); next(); }});Copy the code

Although the front end can control the route permission through vue-Router, it is actually a pseudo-permission control and cannot achieve complete control. Back-end control is strongly recommended for systems that need to control routing permissions.