diff --git a/.env b/.env index 34e263ca..d681a1d9 100644 --- a/.env +++ b/.env @@ -7,7 +7,7 @@ VITE_APP_TITLE=Soybean管理系统 VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版 # 权限路由模式: static | dynamic -VITE_AUTH_ROUTE_MODE=static +VITE_AUTH_ROUTE_MODE=dynamic VITE_VISUALIZER=false diff --git a/components.d.ts b/components.d.ts index 47f42fc2..01abe218 100644 --- a/components.d.ts +++ b/components.d.ts @@ -13,9 +13,14 @@ declare module 'vue' { IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default'] IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-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'] + 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'] IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default'] + IconCustomNetworkError: typeof import('~icons/custom/network-error')['default'] IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default'] IconCustomNotFound: typeof import('~icons/custom/not-found')['default'] IconCustomServiceError: typeof import('~icons/custom/service-error')['default'] @@ -50,6 +55,8 @@ declare module 'vue' { NColorPicker: typeof import('naive-ui')['NColorPicker'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDataTable: typeof import('naive-ui')['NDataTable'] + NDescriptions: typeof import('naive-ui')['NDescriptions'] + NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem'] NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NDivider: typeof import('naive-ui')['NDivider'] NDrawer: typeof import('naive-ui')['NDrawer'] @@ -62,18 +69,26 @@ declare module 'vue' { NGrid: typeof import('naive-ui')['NGrid'] NGridItem: typeof import('naive-ui')['NGridItem'] NInput: typeof import('naive-ui')['NInput'] + NInputGroup: typeof import('naive-ui')['NInputGroup'] NInputNumber: typeof import('naive-ui')['NInputNumber'] + NList: typeof import('naive-ui')['NList'] + NListItem: typeof import('naive-ui')['NListItem'] NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] NMenu: typeof import('naive-ui')['NMenu'] NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NModal: typeof import('naive-ui')['NModal'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] + NPopover: typeof import('naive-ui')['NPopover'] NScrollbar: typeof import('naive-ui')['NScrollbar'] NSelect: typeof import('naive-ui')['NSelect'] NSpace: typeof import('naive-ui')['NSpace'] + NSpin: typeof import('naive-ui')['NSpin'] + NStatistic: typeof import('naive-ui')['NStatistic'] NSwitch: typeof import('naive-ui')['NSwitch'] NTabPane: typeof import('naive-ui')['NTabPane'] NTabs: typeof import('naive-ui')['NTabs'] + NTag: typeof import('naive-ui')['NTag'] + NThing: typeof import('naive-ui')['NThing'] NTimeline: typeof import('naive-ui')['NTimeline'] NTimelineItem: typeof import('naive-ui')['NTimelineItem'] NTooltip: typeof import('naive-ui')['NTooltip'] diff --git a/mock/api/route.ts b/mock/api/route.ts index dbafc7bf..f97432a6 100644 --- a/mock/api/route.ts +++ b/mock/api/route.ts @@ -1,4 +1,5 @@ import type { MockMethod } from 'vite-plugin-mock'; +import { filterAuthRoutesByUserPermission } from '../utils'; const routes: AuthRoute.Route[] = [ { @@ -241,7 +242,7 @@ const routes: AuthRoute.Route[] = [ path: '/auth-demo/permission', component: 'self', meta: { - title: '指令和权限切换', + title: '权限切换', requiresAuth: true, icon: 'ic:round-construction' } @@ -378,12 +379,12 @@ const routes: AuthRoute.Route[] = [ function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route { const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis'; - function sortRoutes(sorts: AuthRoute.Route[]) { - return sorts.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order)); - } + data.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order)); + + const filters = filterAuthRoutesByUserPermission(data, 'admin'); return { - routes: sortRoutes(data), + routes: filters, home: routeHomeName }; } diff --git a/mock/utils/index.ts b/mock/utils/index.ts new file mode 100644 index 00000000..52943add --- /dev/null +++ b/mock/utils/index.ts @@ -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] : []; +} diff --git a/src/composables/system.ts b/src/composables/system.ts index 8c8a764c..19fe4f98 100644 --- a/src/composables/system.ts +++ b/src/composables/system.ts @@ -1,4 +1,6 @@ import UAParser from 'ua-parser-js'; +import { useAuthStore } from '@/store'; +import { isArray, isString } from '@/utils'; interface AppInfo { /** 项目名称 */ @@ -26,3 +28,27 @@ export function useDeviceInfo() { const result = parser.getResult(); 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 + }; +} diff --git a/src/directives/permission.ts b/src/directives/permission.ts index 7b90806c..5585586e 100644 --- a/src/directives/permission.ts +++ b/src/directives/permission.ts @@ -1,26 +1,24 @@ import type { App, Directive } from 'vue'; -import { useAuthStore } from '@/store'; -import { isArray, isString } from '@/utils'; +import { usePermission } from '@/composables'; 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 = { - mounted(el: HTMLElement, binding) { - const { userRole } = auth.userInfo; - const elPermission = binding.value; - let hasPermission = userRole === 'super'; - if (!hasPermission) { - if (isArray(elPermission)) { - hasPermission = (elPermission as Auth.RoleType[]).includes(userRole); - } - if (isString(elPermission)) { - hasPermission = (elPermission as Auth.RoleType) === userRole; - } - } - if (!hasPermission) { - el.remove(); - } + mounted(el, binding) { + updateElVisible(el, binding.value); + }, + beforeUpdate(el, binding) { + updateElVisible(el, binding.value); } }; diff --git a/src/router/modules/auth-demo.ts b/src/router/modules/auth-demo.ts index 07640bca..60fa64c6 100644 --- a/src/router/modules/auth-demo.ts +++ b/src/router/modules/auth-demo.ts @@ -8,7 +8,7 @@ const authDemo: AuthRoute.Route = { path: '/auth-demo/permission', component: 'self', meta: { - title: '指令和权限切换', + title: '权限切换', requiresAuth: true, icon: 'ic:round-construction' } diff --git a/src/store/modules/tab/index.ts b/src/store/modules/tab/index.ts index cdd58343..6acabc59 100644 --- a/src/store/modules/tab/index.ts +++ b/src/store/modules/tab/index.ts @@ -53,7 +53,7 @@ export const useTabStore = defineStore('tab-store', { initHomeTab(routeHomeName: string, router: Router) { const routes = router.getRoutes(); const findHome = routes.find(item => item.name === routeHomeName); - if (findHome && !findHome.children) { + if (findHome && !findHome.children.length) { // 有子路由的不能作为Tab this.homeTab = getTabRouteByVueRoute(findHome); } diff --git a/src/views/auth-demo/permission/index.vue b/src/views/auth-demo/permission/index.vue index ccc169cd..7425cc2d 100644 --- a/src/views/auth-demo/permission/index.vue +++ b/src/views/auth-demo/permission/index.vue @@ -1,17 +1,9 @@ @@ -26,9 +34,11 @@