The business scenario
Requirements can be described as follows:
- Routes are dynamically filtered based on permissions
- Control whether components are displayed based on permissions
Our default backend permission interface is available and returns a tiled array of permission entities, not a tree structure
Technology stack
- Vue
- Vuex
- Vue Router
- TypeScript
It’s 2021, don’t ask how to write in JS again
plan
Use Vuex to obtain and save user permissions
Types /index.d.ts:
export interface Permission {
code: string;
}
Copy the code
Step 2: Create vuex user module. The store directory structure is as follows:
store/modules/user.ts:
import { MutationTree, GetterTree, ActionTree, Module } from 'vuex';
// The type of the permission entity defined in step 1
import { Permission } from '@/types';
// Request the back-end interface
import * as authApi from '@/apis/auth';
export interface UserState {
permissions: Permission[];
}
const state: UserState = {
permissions: []};const getters: GetterTree<UserState, any> = {
permissions(state): Permission[] {
returnstate.permissions; }};const mutations: MutationTree<UserState> = {
SET_PERMISSIONS(state, permissions){ state.permissions = permissions; }};const actions: ActionTree<UserState, any> = {
permissions({ commit }) {
return new Promise((resolve, reject) = > {
authApi
.permissions()
.then((result) = > {
commit('SET_PERMISSIONS', result);
resolve(result);
})
.catch((err) = >{ reject(err); }); }); }};const user: Module<UserState, any> = {
state,
getters,
mutations,
actions
};
export default user;
Copy the code
store/index.ts:
import Vue from 'vue';
import Vuex from 'vuex';
import user from '@/store/modules/user';
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
user
}
});
Copy the code
Routes are dynamically filtered based on permissions
The router file structure is as follows:
Create dynamic and static configurations, router/config.ts:
import { RouteConfig } from 'vue-router';
// Dynamic route configuration
export const asyncRoutes: RouteConfig[] = [
{
path: '/'.meta: { title: 'menu.home' },
component: (a) :any= > import('@/layouts/index.vue'),
children: [{path: 'home'.name: 'Home'.component: (a) :any= > import('@/views/Home.vue'),
// Permissions is the permission code for the menu
meta: { title: 'home'.permissions: ['home']}}, {path: 'about'.name: 'About'.component: (a) :any= > import('@/views/About.vue'),
meta: { title: 'about'.permissions: ['about'}}];// Configure the static route
export const constantRoutes: RouteConfig[] = [];
Copy the code
Second, let’s adjust the router/index.ts:
import Vue from 'vue';
import VueRouter from 'vue-router';
// Import static route configurations without permission control
import { constantRoutes } from '@/router/config';
Vue.use(VueRouter);
const router = new VueRouter({
routes: constantRoutes
});
export default router;
Copy the code
Step 3: Create router/guard.ts:
import router from '@/router';
import store from '@/store';
import { asyncRoutes } from '@/router/config';
import { Permission } from '@/types';
import { RouteConfig } from 'vue-router';
router.beforeEach((to, from, next) = > {
if (to.path === '/user/login') {
next({ path: '/' });
} else {
// If the array of permissions in vuex is empty, get it again from the background interface
if (store.getters.permissions.length === 0) {
store
.dispatch('permissions')
.then((res) = >{ router.addRoutes(filterAsyncRouters(asyncRoutes, res)); next(); })}else{ next(); }}});/** * Filter dynamic routes **@param Routers Dynamic route configuration *@param Permissions array of permission entities *@returns Filtered route configuration array */
function filterAsyncRouters(routers: RouteConfig[], permissions: Permission[]) :RouteConfig[] {
const result: RouteConfig[] = [];
routers.forEach((route) = > {
const newRoute = Object.assign({}, route);
if (hasPermission(newRoute, permissions)) {
result.push(newRoute);
if(newRoute.children && newRoute.children.length) { newRoute.children = filterAsyncRouters(newRoute.children, permissions); }}});return result;
}
/** * Check whether the route permission is granted **@param Route Route entity *@param Permissions array of permission entities *@returns Boolean Whether permissions are available. True Yes, false No */
function hasPermission(route: RouteConfig, permissions: Permission[]) :boolean {
let flag = true;
if (route.meta && route.meta.permissions) {
flag = false;
for (const permission of permissions) {
if (route.meta.permissions.includes(permission.code)) {
flag = true;
break; }}}return flag;
}
Copy the code
As a final step, we need to set the guard to work in main.ts:
import Vue from 'vue';
import App from '@/App.vue';
import store from '@/store';
import router from '@/router';
// Import the guard code
import '@/router/guard';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) = > h(App)
}).$mount('#app');
Copy the code
Control whether components are displayed based on permissions
The first step is to create the content related to the instruction. The structure is as follows:
The second step, write permissions instructions, directives/permission. Ts:
import Vue from 'vue';
import store from '@/store';
// The earliest defined permission entity type
import { Permission } from '@/types';
const permission = Vue.directive('permission', {
inserted: function (el, binding, vnode) {
const permissionCode = binding.arg || ' ';
const permissionCodes: string[] = store.getters.permissions.map(
(permission: Permission) = > {
returnpermission.code; });if(! permissionCodes.includes(permissionCode)) { (el.parentNode && el.parentNode.removeChild(el)) || (el.style.display ='none'); }}});Copy the code
The last step, similar to the router, is to load this directive, main.ts:
import Vue from 'vue';
import App from '@/App.vue';
import store from '@/store';
import router from '@/router';
import '@/router/guard';
// Import the instruction
import '@/directives/action';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) = > h(App)
}).$mount('#app');
Copy the code
The command to use is simple, app.vue:
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<! -- v -- permision for instruction name, about for permission code -->
<router-link v-permission:about to="/about">About</router-link>
</div>
<router-view />
</div>
</template>
Copy the code
All source code for the project can be viewed on Github