mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-18 09:46:36 +08:00
feat(projects): mock添加权限过滤
This commit is contained in:
parent
807448aec5
commit
7f4350aeb6
2
.env
2
.env
@ -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
15
components.d.ts
vendored
@ -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']
|
||||||
|
@ -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
24
mock/utils/index.ts
Normal 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] : [];
|
||||||
|
}
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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' },
|
||||||
|
Loading…
Reference in New Issue
Block a user