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.