From 4358f7809a683125c5e894ee829d01a790bc365b Mon Sep 17 00:00:00 2001 From: hh <2587203630@qq.com> Date: Wed, 15 Jul 2026 15:13:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E6=9D=83=E9=99=90=E9=97=AA=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/frontend/src/App.tsx | 13 +++++++++---- core/frontend/src/routes/route-config.ts | 7 +++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/core/frontend/src/App.tsx b/core/frontend/src/App.tsx index 5eb5115..4850585 100644 --- a/core/frontend/src/App.tsx +++ b/core/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { api, ApiError, getToken, setToken } from "./api"; import { IconKitSvg } from "./components/IconKitSvg"; import type { @@ -41,7 +41,7 @@ import { TeamPage } from "./routes"; import type { AuthMode, NavigateOptions, Notice, Page, ResolvedRoute } from "./routes/route-config"; -import { pathForPage, resolveRoute, routeLabels } from "./routes/route-config"; +import { isOwnerOnlyPage, pathForPage, resolveRoute, routeLabels } from "./routes/route-config"; import { AdminApp } from "./routes/admin/admin-app"; import { TrashPage } from "./routes/trash"; import { ModelsPage } from "./routes/models"; @@ -322,9 +322,9 @@ export function App() { // 子账号(非主账号)纠回:团队 / 消费 页仅主账号(owner=超管)可见,子账号直接拉回工作台(PMC#3)。 // role 为空时(身份尚未带回角色)不拦,避免误纠超管。 - useEffect(() => { + useLayoutEffect(() => { if (booting || !user || !role) return; - if (!isOwner && (page === "team" || page === "account")) { + if (!isOwner && isOwnerOnlyPage(page)) { navigate("dashboard", { replace: true }); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -448,6 +448,11 @@ export function App() { }, [activeProjectId]); function navigate(next: Page, options: NavigateOptions = {}) { + // 已知为子账号时在发起导航前拦截;与直接访问 URL 的 layout guard 共用同一规则。 + if (role && !isOwner && isOwnerOnlyPage(next)) { + setNotice({ type: "info", text: "当前账号暂无访问权限" }); + return; + } const productId = options.productId ?? activeProductId; const projectId = options.projectId ?? activeProjectId; if (options.productId !== undefined) setActiveProductId(options.productId); diff --git a/core/frontend/src/routes/route-config.ts b/core/frontend/src/routes/route-config.ts index a83c16b..279e2fe 100644 --- a/core/frontend/src/routes/route-config.ts +++ b/core/frontend/src/routes/route-config.ts @@ -61,6 +61,13 @@ export type NavigateFn = (page: Page, options?: NavigateOptions) => void; export type Notice = { type: "success" | "error" | "info"; text: string } | null; export type NavItem = { page: Page; label: string; icon: typeof Home; badge?: string }; +// 仅团队主账号可访问的团队级页面。入口与路由守卫共用此规则,避免权限逻辑分散。 +const OWNER_ONLY_PAGES = new Set(["team", "account"]); + +export function isOwnerOnlyPage(page: Page) { + return OWNER_ONLY_PAGES.has(page); +} + export const mainNav: NavItem[] = [ { page: "dashboard", label: "工作台", icon: Home }, { page: "products", label: "商品库", icon: Package },