From 9f19bea7ee4f4751284bf9bfbad69c89ddfe3505 Mon Sep 17 00:00:00 2001 From: xiong Date: Mon, 3 Aug 2026 14:03:44 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=89=8D=E7=AB=AF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/layout/AppLayout.test.tsx | 21 ++++++++++++++++++- .../src/layout/AppLayout.tsx | 21 +++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Tiku.PlatformAdmin.Web/src/layout/AppLayout.test.tsx b/Tiku.PlatformAdmin.Web/src/layout/AppLayout.test.tsx index a640ce7..a24f66f 100644 --- a/Tiku.PlatformAdmin.Web/src/layout/AppLayout.test.tsx +++ b/Tiku.PlatformAdmin.Web/src/layout/AppLayout.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import type { components } from '../api/schema.generated'; -import { visibleNavigationPaths } from './AppLayout'; +import { frontendPathForMenu, visibleNavigationPaths } from './AppLayout'; type BackofficeMenuItem = components['schemas']['BackofficeMenuItem']; @@ -20,6 +20,25 @@ function menu(path: string, permissionCode: string, isActive = true): Backoffice } describe('平台导航授权', () => { + it('将后端平台菜单路径转换为当前前端路由', () => { + expect(frontendPathForMenu('/platform/dashboard')).toBe('/'); + expect(frontendPathForMenu('/platform/tenants')).toBe('/tenants'); + expect(frontendPathForMenu('/platform/question-banks/')).toBe('/question-banks'); + expect(frontendPathForMenu('/platform/saas')).toBe('/subscriptions'); + expect(frontendPathForMenu('/platform/payment-settings')).toBe('/payments'); + }); + + it('显示后端返回的带平台前缀菜单', () => { + const granted = new Set(['platform:dashboard:view', 'platform:tenant:manage', 'platform:payment:read']); + const paths = visibleNavigationPaths([ + menu('/platform/dashboard', 'platform:dashboard:view'), + menu('/platform/tenants', 'platform:tenant:manage'), + menu('/platform/payment-settings', 'platform:payment:read'), + ], (...permissions) => permissions.some((permission) => granted.has(permission))); + + expect(paths).toEqual(['/', '/tenants', '/payments']); + }); + it('同时以 ui-bootstrap 菜单和权限作为可见性边界', () => { const granted = new Set(['platform:dashboard:view', 'platform:tenant:manage', 'platform:audit:view']); const paths = visibleNavigationPaths([ diff --git a/Tiku.PlatformAdmin.Web/src/layout/AppLayout.tsx b/Tiku.PlatformAdmin.Web/src/layout/AppLayout.tsx index 08e6dff..b20d13b 100644 --- a/Tiku.PlatformAdmin.Web/src/layout/AppLayout.tsx +++ b/Tiku.PlatformAdmin.Web/src/layout/AppLayout.tsx @@ -55,11 +55,26 @@ const navigation = [ type BackofficeMenuItem = components['schemas']['BackofficeMenuItem']; +const backendMenuPathAliases: Record = { + '/platform/dashboard': '/', + '/platform/saas': '/subscriptions', + '/platform/payment-settings': '/payments', +}; + +export function frontendPathForMenu(path: string): string { + const normalizedPath = path.length > 1 ? path.replace(/\/+$/, '') : path; + if (backendMenuPathAliases[normalizedPath]) return backendMenuPathAliases[normalizedPath]; + if (normalizedPath.startsWith('/platform/')) return normalizedPath.slice('/platform'.length); + return normalizedPath; +} + export function visibleNavigationPaths( menus: readonly BackofficeMenuItem[], hasPermission: (...permissions: string[]) => boolean, ): string[] { - const enabledPaths = new Set(menus.filter((menu) => menu.isActive && menu.path).map((menu) => menu.path as string)); + const enabledPaths = new Set(menus + .filter((menu) => menu.isActive && menu.path) + .map((menu) => frontendPathForMenu(menu.path as string))); return navigation.flatMap((item) => { if ('permission' in item && item.permission && item.key) { return enabledPaths.has(item.key) && hasPermission(item.permission) ? [item.key] : []; @@ -76,7 +91,9 @@ function visibleNavigation( ): MenuProps['items'] { const items: NonNullable = []; const visiblePaths = new Set(visibleNavigationPaths(menus, hasPermission)); - const menuByPath = new Map(menus.filter((menu) => menu.isActive && menu.path).map((menu) => [menu.path as string, menu])); + const menuByPath = new Map(menus + .filter((menu) => menu.isActive && menu.path) + .map((menu) => [frontendPathForMenu(menu.path as string), menu])); for (const item of navigation) { if ('permission' in item && item.permission && item.key) { if (visiblePaths.has(item.key)) items.push({ key: item.key, icon: item.icon, label: menuByPath.get(item.key)?.title || item.label });