mirror of
https://github.com/dataelement/bisheng.git
synced 2026-09-17 17:18:35 +08:00
fix(platform): restore child admin menu access
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: () => <span aria-hidden="true" />,
|
||||
}))
|
||||
|
||||
describe("HeaderMenu for Child Admin", () => {
|
||||
it("shows application, tool, and workbench tabs without role web_menu grants", () => {
|
||||
const user = {
|
||||
user_id: 1,
|
||||
user_name: "child-admin",
|
||||
role: "user",
|
||||
web_menu: [],
|
||||
is_child_admin: true,
|
||||
}
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/build/apps"]}>
|
||||
<userContext.Provider value={{ user } as any}>
|
||||
<HeaderMenu />
|
||||
</userContext.Provider>
|
||||
</MemoryRouter>,
|
||||
)
|
||||
|
||||
expect(screen.getByText("build.app")).toBeInTheDocument()
|
||||
expect(screen.getByText("build.tools")).toBeInTheDocument()
|
||||
expect(screen.getByText("build.workbench")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -145,7 +145,7 @@ function renderLayout(userOverrides: Record<string, unknown> = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
describe("MainLayout system / tenant nav for Child Admin (PRD §3.3)", () => {
|
||||
describe("MainLayout admin nav for Child Admin", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
Object.defineProperty(window, "localStorage", {
|
||||
@@ -158,26 +158,25 @@ describe("MainLayout system / tenant nav for Child Admin (PRD §3.3)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("Child Admin sees the system menu entry", () => {
|
||||
renderLayout({ is_child_admin: true });
|
||||
expect(screen.getByText("menu.system")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Child Admin sees the build entry for workstation config", () => {
|
||||
renderLayout({ is_child_admin: true });
|
||||
expect(screen.getByText("menu.skills")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Child Admin does NOT see the tenant management entry (super-only until backend is opened up)", () => {
|
||||
it("Child Admin sees every admin menu except tenant management", () => {
|
||||
renderLayout({ is_child_admin: true });
|
||||
for (const label of [
|
||||
"menu.dashboard",
|
||||
"menu.skills",
|
||||
"menu.knowledge",
|
||||
"menu.dataset",
|
||||
"menu.models",
|
||||
"menu.evaluation",
|
||||
"menu.annotation",
|
||||
"menu.log",
|
||||
"menu.approval",
|
||||
"menu.system",
|
||||
]) {
|
||||
expect(screen.getByText(label)).toBeInTheDocument();
|
||||
}
|
||||
expect(screen.queryByText("tenant.management")).toBeNull();
|
||||
});
|
||||
|
||||
it("Child Admin does NOT see the dataset entry (super / dept admin only)", () => {
|
||||
renderLayout({ is_child_admin: true });
|
||||
expect(screen.queryByText("menu.dataset")).toBeNull();
|
||||
});
|
||||
|
||||
it("plain user without any admin flag sees neither system nor tenant management", () => {
|
||||
renderLayout();
|
||||
expect(screen.queryByText("menu.system")).toBeNull();
|
||||
|
||||
@@ -96,11 +96,18 @@ describe("resolveRoutePermissions", () => {
|
||||
expect(perms.filter((p) => p === "model")).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("handles missing web_menu gracefully", () => {
|
||||
it("grants Child Admin every admin route permission except tenant management", () => {
|
||||
expect(resolveRoutePermissions({})).toEqual([])
|
||||
expect(resolveRoutePermissions({ is_child_admin: true })).toEqual([
|
||||
"sys",
|
||||
"board",
|
||||
"build",
|
||||
"create_app",
|
||||
"knowledge",
|
||||
"model",
|
||||
"evaluation",
|
||||
"mark_task",
|
||||
"log",
|
||||
"sys",
|
||||
"workstation",
|
||||
])
|
||||
})
|
||||
@@ -116,6 +123,6 @@ describe("resolveRoutePermissions", () => {
|
||||
})
|
||||
expect(perms).toContain("sys")
|
||||
expect(perms).toContain("workstation")
|
||||
expect(perms).not.toContain("create_app")
|
||||
expect(perms).toContain("create_app")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user