diff --git a/src/frontend/platform/src/App.tsx b/src/frontend/platform/src/App.tsx index 4afc71f64..61951da96 100644 --- a/src/frontend/platform/src/App.tsx +++ b/src/frontend/platform/src/App.tsx @@ -170,7 +170,7 @@ export default function App() { const noAuthPages = ['chat', 'resouce'] const path = location.pathname.replace(__APP_ENV__.BASE_URL, '').split('/')?.[1] || '' - // 动态路由根据权限:部门管理员补 create_app、子租户管理员补 sys(详见 resolveRoutePermissions) + // Resolve route permissions with fallbacks for department and child tenant admins. const router = useMemo(() => { if (user && user.role === 'admin') return getAdminRouter() if (!user?.user_id) return null diff --git a/src/frontend/platform/src/layout/HeaderMenu.tsx b/src/frontend/platform/src/layout/HeaderMenu.tsx index 87983903f..025424d0e 100644 --- a/src/frontend/platform/src/layout/HeaderMenu.tsx +++ b/src/frontend/platform/src/layout/HeaderMenu.tsx @@ -15,6 +15,7 @@ export default function HeaderMenu({ }) { return user.role === 'admin' }, [user]) const canOpenBuild = isAdmin + || Boolean(user.is_child_admin) || Boolean(user.web_menu?.includes('build')) || Boolean(user.web_menu?.includes('create_app')) const canManageWorkbench = canManageWorkbenchConfig(user) diff --git a/src/frontend/platform/src/layout/MainLayout.tsx b/src/frontend/platform/src/layout/MainLayout.tsx index d8ddab3a5..75f8b6bc5 100755 --- a/src/frontend/platform/src/layout/MainLayout.tsx +++ b/src/frontend/platform/src/layout/MainLayout.tsx @@ -69,16 +69,16 @@ export default function MainLayout() { navigator('/reset') } - // 系统超管(租户超管仍走自定义角色 web_menu;部门管理员由服务端合并全量菜单) + // Super admins and child tenant admins receive the full admin menu. + // Tenant management remains super-admin-only; department admins receive menus from the backend. const isSuperAdmin = useMemo(() => user.role === "admin", [user]) const isDeptAdmin = Boolean(user.is_department_admin) const isChildAdmin = Boolean(user.is_child_admin) const canManageWorkbenchConfig = isSuperAdmin || isChildAdmin - // 侧栏:数据集 / 日志 — 超管与部门管理员 - const isFullAdminShell = isSuperAdmin || isDeptAdmin - // 侧栏:系统管理 — 超管 / 部门管理员 / Child Admin。SystemPage 内部按 - // PRD §3.3 已为 Child Admin 分了 Tab 视角(组织 + 角色) - const showSystemNav = isFullAdminShell || isChildAdmin + // Covers admin entries such as datasets that have no independent web_menu route key. + const isFullAdminShell = isSuperAdmin || isDeptAdmin || isChildAdmin + // SystemPage limits each admin type to the tabs it can manage. + const showSystemNav = isFullAdminShell // 审批管理 — 仅超管 / Child Admin(部门管理员不可见) const showApprovalNav = isSuperAdmin || isChildAdmin // Admin-area approval scope (falls back to the legacy global flag for @@ -96,7 +96,7 @@ export default function MainLayout() { || user.web_menu?.includes('frontend') || canManageWorkbenchConfig } - return user.web_menu?.includes(menu) || isSuperAdmin + return user.web_menu?.includes(menu) || isSuperAdmin || isChildAdmin } const u = user as User diff --git a/src/frontend/platform/src/routes/index.tsx b/src/frontend/platform/src/routes/index.tsx index d5d728907..0f87943b1 100755 --- a/src/frontend/platform/src/routes/index.tsx +++ b/src/frontend/platform/src/routes/index.tsx @@ -173,13 +173,25 @@ function hasRoutePermission(permissions: string[], key: string) { } /** - * 把后端下发的 web_menu 转成路由层用的 permissions 数组。 + * Convert the backend web_menu value into route permissions. * - * - 部门管理员补 `create_app`(后端来不及下发时的兜底,原有行为)。 - * - 子租户管理员(Child Admin)补 `sys` / `model`:后端 web_menu 不下发 sys/system_config 给非 - * 超管/非部门管理员,且默认不下发 `model` / `workstation` 资源。子租户管理员需要在自己 - * 租户内管理模型和工作台配置,路由层放行后由后端各自的 tenant admin 校验实际权限。 + * - Department admins receive a create_app fallback when the backend omits it. + * - Child tenant admins receive every admin route except tenant management. Their backend web_menu + * still comes from a regular role, while business APIs retain tenant and resource authorization. */ +const CHILD_ADMIN_ROUTE_PERMISSIONS = [ + "board", + "build", + "create_app", + "knowledge", + "model", + "evaluation", + "mark_task", + "log", + "sys", + "workstation", +] as const + export function resolveRoutePermissions(user: { web_menu?: string[] is_department_admin?: boolean | null @@ -189,14 +201,9 @@ export function resolveRoutePermissions(user: { if (user.is_department_admin && !perms.includes("create_app")) { perms = [...perms, "create_app"] } - if (user.is_child_admin && !perms.includes("sys")) { - perms = [...perms, "sys"] - } - if (user.is_child_admin && !perms.includes("model")) { - perms = [...perms, "model"] - } - if (user.is_child_admin && !perms.includes("workstation")) { - perms = [...perms, "workstation"] + if (user.is_child_admin) { + const missing = CHILD_ADMIN_ROUTE_PERMISSIONS.filter((permission) => !perms.includes(permission)) + if (missing.length) perms = [...perms, ...missing] } return perms } diff --git a/src/frontend/platform/src/test/headerMenuChildAdmin.test.tsx b/src/frontend/platform/src/test/headerMenuChildAdmin.test.tsx new file mode 100644 index 000000000..00f58fc6c --- /dev/null +++ b/src/frontend/platform/src/test/headerMenuChildAdmin.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from "@testing-library/react" +import { MemoryRouter } from "react-router-dom" +import { describe, expect, it, vi } from "vitest" + +import { userContext } from "@/contexts/userContext" +import HeaderMenu from "@/layout/HeaderMenu" + +vi.hoisted(() => { + ;(globalThis as any).__APP_ENV__ = { BASE_URL: "" } +}) + +vi.mock("@/components/bs-icons", () => ({ + TabIcon: () =>