fix(admin): 登录后完整菜单 + 刷新保持当前Tab

- navForRole: 持平台令牌(operator)即恒显示完整运营菜单,不再被残留园区凭据(isParkUser)误判为园区端
- 新增 useUrlTab: 选区同步到 ?tab= ,刷新/回退均保持
- 算力中心 compute 与 园区管理 park 的 Tab 改用受控值 useUrlTab
This commit is contained in:
Pine
2026-08-25 22:57:53 +08:00
parent f03e3a3fed
commit dc79586c10
4 changed files with 42 additions and 6 deletions
+6 -4
View File
@@ -70,15 +70,17 @@ export const NAV: NavGroup[] = [
// 按当前身份角色过滤导航
export function navForRole(role: string | null): NavGroup[] {
const { isAuthed } = useAuth();
// 平台管理方:恒显示完整运营管理导航(角色暂未加载时也按 operator 视作,避免「只见园区管理」)
if (isAuthed && !isParkUser()) {
const r = role || "operator";
// 平台管理方:持平台令牌即恒显示完整运营管理导航
// 不再叠加 !isParkUser() —— 避免「本会话曾登录过园区端/残留园区凭据」导致登录后只见园区管理,
// 只有刷新/重新拉取身份后才恢复。平台令牌存在即平台优先。
if (isAuthed) {
const r = role === "operator" ? role : "operator";
return NAV.map((g) => ({
...g,
items: g.items.filter((i) => !i.roles || i.roles.includes(r)),
})).filter((g) => g.items.length > 0);
}
// 园区端(园区账密登录,无平台角色):只显示园区管理(本园区)
// 园区端(园区账密登录,无平台令牌):只显示园区管理(本园区)
if (isParkUser()) {
return [{ title: "园区管理", items: [{ to: "/park", label: "园区管理", icon: Building2 }] }];
}
+31
View File
@@ -0,0 +1,31 @@
import * as React from "react";
import { useSearchParams } from "react-router-dom";
/**
* URL 驱动的 Tab 状态:把当前选中项同步到 ``?tab=``,刷新/回退均保持。
* key 默认「tab」,可用 ``param`` 覆盖(如一页内多个 Tab 组)。
*
* 用法:
* const [tab, setTab] = useUrlTab("screens");
* <Tabs value={tab} onValueChange={setTab}>...
*/
export function useUrlTab(defaultKey: string, param = "tab"): [string, (v: string) => void] {
const [searchParams, setSearchParams] = useSearchParams();
const current = searchParams.get(param) || defaultKey;
const setTab = React.useCallback(
(v: string) => {
setSearchParams(
(prev) => {
const next = new URLSearchParams(prev);
next.set(param, v);
return next;
},
{ replace: true },
);
},
[setSearchParams, param],
);
return [current, setTab];
}
+2 -1
View File
@@ -7,6 +7,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Loader2 } from "lucide-react";
import { computeApi } from "@/api/resources";
import { useUrlTab } from "@/lib/useUrlTab";
import ChannelsPage from "./channels";
import StatsPage from "./stats";
import ModelsPage from "./models";
@@ -35,7 +36,7 @@ const TABS = [
];
export default function ComputePage() {
const [tab, setTab] = React.useState("overview");
const [tab, setTab] = useUrlTab("overview", "tab");
return (
<div>
+3 -1
View File
@@ -3,6 +3,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { ParkTenantProvider, useParkTenant } from "./tenant-context";
import { useUrlTab } from "@/lib/useUrlTab";
import { TenantTab } from "./tenant-tab";
import { ScreensTab } from "./screens-tab";
import { ScreenTab } from "./screen-tab";
@@ -35,13 +36,14 @@ function TenantBar() {
function ParkPageInner() {
const { tenantId, isParkMode } = useParkTenant();
const [tab, setTab] = useUrlTab(isParkMode ? "screens" : "tenant");
return (
<div>
<PageHeader title="园区管理" description={isParkMode ? "园区端:管理本园区屏幕与数据" : "运营端:全园区管理(创建园区、配置各园区)"} />
<TenantBar />
<Card>
<CardContent className="pt-4">
<Tabs defaultValue={isParkMode ? "screens" : "tenant"}>
<Tabs value={tab} onValueChange={setTab}>
<TabsList>
{!isParkMode && <TabsTrigger value="tenant"></TabsTrigger>}
<TabsTrigger value="screens"></TabsTrigger>