业务场景
需求可具体描述为如下内容:
- 根据权限动态筛选路由
- 根据权限控制组件的是否展示
我们默认后端权限接口可用, 且返回的是权限实体平铺数组, 并非树形结构
技术栈
- Vue
- Vuex
- Vue Router
- TypeScript
都2021年啦, 不要再问用js怎么写了
方案
使用Vuex获取并保存用户权限
第一步, 定义权限实体类型, types/index.d.ts:
export interface Permission {
code: string;
}
复制代码
第二步, 创建vuex的user模块, store目录结构如下:
store/modules/user.ts:
import { MutationTree, GetterTree, ActionTree, Module } from 'vuex';
// 第一步定义的权限实体类型
import { Permission } from '@/types';
// 请求后端接口
import * as authApi from '@/apis/auth';
export interface UserState {
permissions: Permission[];
}
const state: UserState = {
permissions: []
};
const getters: GetterTree<UserState, any> = {
permissions(state): Permission[] {
return state.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;
复制代码
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
}
});
复制代码
根据权限动态筛选路由
第一步, 我们来改造下router, 文件结构如下:
创建动态和静态配置, router/config.ts:
import { RouteConfig } from 'vue-router';
// 动态路由配置
export const asyncRoutes: RouteConfig[] = [
{
path: '/',
meta: { title: 'menu.home' },
component: (): any => import('@/layouts/index.vue'),
children: [
{
path: 'home',
name: 'Home',
component: (): any => import('@/views/Home.vue'),
// permissions就是这个菜单的权限代码
meta: { title: 'home', permissions: ['home'] }
},
{
path: 'about',
name: 'About',
component: (): any => import('@/views/About.vue'),
meta: { title: 'about', permissions: ['about'] }
}
]
}
];
// 静态路由配置
export const constantRoutes: RouteConfig[] = [];
复制代码
第二步, 我们调整下router/index.ts:
import Vue from 'vue';
import VueRouter from 'vue-router';
// 导入静态路由配置, 不需要控制权限
import { constantRoutes } from '@/router/config';
Vue.use(VueRouter);
const router = new VueRouter({
routes: constantRoutes
});
export default router;
复制代码
第三步, 创建路由前置守卫, 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 {
// 如果vuex中的权限数组为空, 则从后台接口重新获取一次
if (store.getters.permissions.length === 0) {
store
.dispatch('permissions')
.then((res) => {
router.addRoutes(filterAsyncRouters(asyncRoutes, res));
next();
})
} else {
next();
}
}
});
/**
* 筛选动态路由
*
* @param routers 动态路由配置
* @param permissions 权限实体数组
* @returns 筛选后的路由配置数组
*/
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;
}
/**
* 判断是否拥有路由权限
*
* @param route 路由实体
* @param permissions 权限实体数组
* @returns boolean 是否拥有权限 true 是, false 否
*/
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;
}
复制代码
最后一步, 需要在main.ts中让这个守卫生效:
import Vue from 'vue';
import App from '@/App.vue';
import store from '@/store';
import router from '@/router';
// 导入守卫代码
import '@/router/guard';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App)
}).$mount('#app');
复制代码
根据权限控制组件的是否展示
第一步, 创建指令相关内容, 结构如下:
第二步, 编写权限指令, directives/permission.ts:
import Vue from 'vue';
import store from '@/store';
// 最早定义的权限实体类型
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) => {
return permission.code;
}
);
if (!permissionCodes.includes(permissionCode)) {
(el.parentNode && el.parentNode.removeChild(el)) ||
(el.style.display = 'none');
}
}
});
复制代码
最后一步, 和router中类似, 我们要加载这个指令, main.ts:
import Vue from 'vue';
import App from '@/App.vue';
import store from '@/store';
import router from '@/router';
import '@/router/guard';
// 导入指令
import '@/directives/action';
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App)
}).$mount('#app');
复制代码
使用指令很简单, App.vue:
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<!-- v-permision为指令名称, about为权限代码 -->
<router-link v-permission:about to="/about">About</router-link>
</div>
<router-view />
</div>
</template>
复制代码
项目所有源码可以在Github上查看
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END