feat(projects): mock添加权限过滤

This commit is contained in:
Soybean 2022-04-23 12:29:09 +08:00
parent 807448aec5
commit 7f4350aeb6
9 changed files with 109 additions and 35 deletions

2
.env
View File

@ -7,7 +7,7 @@ VITE_APP_TITLE=Soybean管理系统
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版 VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
# 权限路由模式: static dynamic # 权限路由模式: static dynamic
VITE_AUTH_ROUTE_MODE=static VITE_AUTH_ROUTE_MODE=dynamic
VITE_VISUALIZER=false VITE_VISUALIZER=false

15
components.d.ts vendored
View File

@ -13,9 +13,14 @@ declare module 'vue' {
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default'] IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default'] IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default'] IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconCustomActivity: typeof import('~icons/custom/activity')['default']
IconCustomAvatar: typeof import('~icons/custom/avatar')['default'] IconCustomAvatar: typeof import('~icons/custom/avatar')['default']
IconCustomBanner: typeof import('~icons/custom/banner')['default']
IconCustomCast: typeof import('~icons/custom/cast')['default']
IconCustomEmptyData: typeof import('~icons/custom/empty-data')['default']
IconCustomLogo: typeof import('~icons/custom/logo')['default'] IconCustomLogo: typeof import('~icons/custom/logo')['default']
IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default'] IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default']
IconCustomNetworkError: typeof import('~icons/custom/network-error')['default']
IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default'] IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default']
IconCustomNotFound: typeof import('~icons/custom/not-found')['default'] IconCustomNotFound: typeof import('~icons/custom/not-found')['default']
IconCustomServiceError: typeof import('~icons/custom/service-error')['default'] IconCustomServiceError: typeof import('~icons/custom/service-error')['default']
@ -50,6 +55,8 @@ declare module 'vue' {
NColorPicker: typeof import('naive-ui')['NColorPicker'] NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable'] NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer'] NDrawer: typeof import('naive-ui')['NDrawer']
@ -62,18 +69,26 @@ declare module 'vue' {
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem'] NGridItem: typeof import('naive-ui')['NGridItem']
NInput: typeof import('naive-ui')['NInput'] NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber'] NInputNumber: typeof import('naive-ui')['NInputNumber']
NList: typeof import('naive-ui')['NList']
NListItem: typeof import('naive-ui')['NListItem']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal'] NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane'] NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTimeline: typeof import('naive-ui')['NTimeline'] NTimeline: typeof import('naive-ui')['NTimeline']
NTimelineItem: typeof import('naive-ui')['NTimelineItem'] NTimelineItem: typeof import('naive-ui')['NTimelineItem']
NTooltip: typeof import('naive-ui')['NTooltip'] NTooltip: typeof import('naive-ui')['NTooltip']

View File

@ -1,4 +1,5 @@
import type { MockMethod } from 'vite-plugin-mock'; import type { MockMethod } from 'vite-plugin-mock';
import { filterAuthRoutesByUserPermission } from '../utils';
const routes: AuthRoute.Route[] = [ const routes: AuthRoute.Route[] = [
{ {
@ -241,7 +242,7 @@ const routes: AuthRoute.Route[] = [
path: '/auth-demo/permission', path: '/auth-demo/permission',
component: 'self', component: 'self',
meta: { meta: {
title: '指令和权限切换', title: '权限切换',
requiresAuth: true, requiresAuth: true,
icon: 'ic:round-construction' icon: 'ic:round-construction'
} }
@ -378,12 +379,12 @@ const routes: AuthRoute.Route[] = [
function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route { function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis'; const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
function sortRoutes(sorts: AuthRoute.Route[]) { data.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
return sorts.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
} const filters = filterAuthRoutesByUserPermission(data, 'admin');
return { return {
routes: sortRoutes(data), routes: filters,
home: routeHomeName home: routeHomeName
}; };
} }

24
mock/utils/index.ts Normal file
View File

@ -0,0 +1,24 @@
/**
*
* @param routes -
* @param permission -
*/
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);
}
/**
*
* @param route -
* @param permission -
*/
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
const hasPermission =
!route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
if (route.children) {
const filterChildren = route.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
Object.assign(route, { children: filterChildren });
}
return hasPermission ? [route] : [];
}

View File

@ -1,4 +1,6 @@
import UAParser from 'ua-parser-js'; import UAParser from 'ua-parser-js';
import { useAuthStore } from '@/store';
import { isArray, isString } from '@/utils';
interface AppInfo { interface AppInfo {
/** 项目名称 */ /** 项目名称 */
@ -26,3 +28,27 @@ export function useDeviceInfo() {
const result = parser.getResult(); const result = parser.getResult();
return result; return result;
} }
/** 权限判断 */
export function usePermission() {
const auth = useAuthStore();
function hasPermission(permission: Auth.RoleType | Auth.RoleType[]) {
const { userRole } = auth.userInfo;
let has = userRole === 'super';
if (!has) {
if (isArray(permission)) {
has = (permission as Auth.RoleType[]).includes(userRole);
}
if (isString(permission)) {
has = (permission as Auth.RoleType) === userRole;
}
}
return has;
}
return {
hasPermission
};
}

View File

@ -1,26 +1,24 @@
import type { App, Directive } from 'vue'; import type { App, Directive } from 'vue';
import { useAuthStore } from '@/store'; import { usePermission } from '@/composables';
import { isArray, isString } from '@/utils';
export default function setupPermissionDirective(app: App) { export default function setupPermissionDirective(app: App) {
const auth = useAuthStore(); const { hasPermission } = usePermission();
function updateElVisible(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) {
if (!permission) {
throw new Error(`need roles: like v-permission="'admin'", v-permission="['admin', 'test]"`);
}
if (!hasPermission(permission)) {
el.parentElement?.removeChild(el);
}
}
const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = { const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = {
mounted(el: HTMLElement, binding) { mounted(el, binding) {
const { userRole } = auth.userInfo; updateElVisible(el, binding.value);
const elPermission = binding.value; },
let hasPermission = userRole === 'super'; beforeUpdate(el, binding) {
if (!hasPermission) { updateElVisible(el, binding.value);
if (isArray(elPermission)) {
hasPermission = (elPermission as Auth.RoleType[]).includes(userRole);
}
if (isString(elPermission)) {
hasPermission = (elPermission as Auth.RoleType) === userRole;
}
}
if (!hasPermission) {
el.remove();
}
} }
}; };

View File

@ -8,7 +8,7 @@ const authDemo: AuthRoute.Route = {
path: '/auth-demo/permission', path: '/auth-demo/permission',
component: 'self', component: 'self',
meta: { meta: {
title: '指令和权限切换', title: '权限切换',
requiresAuth: true, requiresAuth: true,
icon: 'ic:round-construction' icon: 'ic:round-construction'
} }

View File

@ -53,7 +53,7 @@ export const useTabStore = defineStore('tab-store', {
initHomeTab(routeHomeName: string, router: Router) { initHomeTab(routeHomeName: string, router: Router) {
const routes = router.getRoutes(); const routes = router.getRoutes();
const findHome = routes.find(item => item.name === routeHomeName); const findHome = routes.find(item => item.name === routeHomeName);
if (findHome && !findHome.children) { if (findHome && !findHome.children.length) {
// 有子路由的不能作为Tab // 有子路由的不能作为Tab
this.homeTab = getTabRouteByVueRoute(findHome); this.homeTab = getTabRouteByVueRoute(findHome);
} }

View File

@ -1,17 +1,9 @@
<template> <template>
<div class="h-full"> <div class="h-full">
<n-card title="权限指令 v-permission" class="h-full shadow-sm rounded-16px"> <n-card title="权限切换" class="h-full shadow-sm rounded-16px">
<div class="pb-12px"> <div class="pb-12px">
<n-gradient-text type="primary" :size="20">当前用户的权限{{ auth.userInfo.userRole }}</n-gradient-text> <n-gradient-text type="primary" :size="20">当前用户的权限{{ auth.userInfo.userRole }}</n-gradient-text>
</div> </div>
<n-space>
<n-button v-permission="`super`">super可见</n-button>
<n-button v-permission="`admin`">admin可见</n-button>
<n-button v-permission="['admin', 'test']">admin和test可见</n-button>
</n-space>
<div class="py-12px">
<n-gradient-text type="primary" :size="20">切换用户权限</n-gradient-text>
</div>
<n-select <n-select
:value="auth.userInfo.userRole" :value="auth.userInfo.userRole"
class="w-120px" class="w-120px"
@ -19,6 +11,22 @@
:options="roleList" :options="roleList"
@update:value="auth.updateUserRole" @update:value="auth.updateUserRole"
/> />
<div class="py-12px">
<n-gradient-text type="primary" :size="20">权限指令 v-permission</n-gradient-text>
</div>
<div>
<n-button v-permission="'super'" class="mr-12px">super可见</n-button>
<n-button v-permission="'admin'" class="mr-12px">admin可见</n-button>
<n-button v-permission="['admin', 'test']">admin和test可见</n-button>
</div>
<div class="py-12px">
<n-gradient-text type="primary" :size="20">权限函数 hasPermission</n-gradient-text>
</div>
<n-space>
<n-button v-if="hasPermission('super')">super可见</n-button>
<n-button v-if="hasPermission('admin')">admin可见</n-button>
<n-button v-if="hasPermission(['admin', 'test'])">admin和test可见</n-button>
</n-space>
</n-card> </n-card>
</div> </div>
</template> </template>
@ -26,9 +34,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { watch } from 'vue'; import { watch } from 'vue';
import { useAppStore, useAuthStore } from '@/store'; import { useAppStore, useAuthStore } from '@/store';
import { usePermission } from '@/composables';
const app = useAppStore(); const app = useAppStore();
const auth = useAuthStore(); const auth = useAuthStore();
const { hasPermission } = usePermission();
const roleList = [ const roleList = [
{ label: '超级管理员', value: 'super' }, { label: '超级管理员', value: 'super' },