接入统一鉴权中心(Keycloak,ADR-012);活动模块 L1/L2 运营态模版(历史活动列表+检索+沙箱);数据域与工作台界面改版;个人中心 /me

This commit is contained in:
shanqin
2026-08-26 13:29:36 +08:00
parent f3397a60ef
commit f01286a89d
40 changed files with 2502 additions and 480 deletions
+39 -29
View File
@@ -11,10 +11,11 @@
* 刷新与深链都能完整还原;前端零过程状态。
*/
import { useCallback, useEffect, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { Session } from '@contracts';
import { logoutRoute, sessionRoute } from '@contracts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { Principal } from '@contracts';
import { meRoute } from '@contracts';
import { call } from '@web-shared/typed-fetch.ts';
import { logout as kcLogout } from '@web-shared/auth.ts';
import { Ico } from '@web-shared/Ico.tsx';
import { TABS, ZM, ZONES, routeLabel } from '@web-shared/gov/gov-route.ts';
import type { GovZone, Nav, Route } from '@web-shared/gov/gov-route.ts';
@@ -24,14 +25,14 @@ import { useGovObjects, useGovTasks } from '@web-shared/gov/gov-api.ts';
import { domainShort, sortDomains } from '@web-shared/gov/domains.ts';
import { queryKeys } from './query.ts';
import { BlueprintHome } from '@features/gov-blueprint';
import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
import { TasksHome, ActvPage, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
import { RunsHome } from '@features/gov-runs';
import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff';
import { PkgPage, WorkflowsHome } from '@features/gov-process';
import { TriggerPage, TriggerRulesHome } from '@features/gov-trigger';
import { ActPage, LibraryHome } from '@features/gov-activity';
import { ToolPage, ToolsHome } from '@features/gov-tool';
import { DataHome, LedgersHome, ObjPage } from '@features/gov-data';
import { DataHome, LedgersHome, ObjPage, RowPage } from '@features/gov-data';
import { ArchiveHome } from '@features/gov-archive';
import { TestbedHome } from '@features/gov-testbed';
import { CockpitHome } from '@features/gov-memory';
@@ -65,21 +66,20 @@ const writePlace = (surface: Surface, p: Place): string =>
export function GovShell({ surface }: { surface: Surface }) {
const [place, setPlace] = useState<Place>(() => readPlace(surface));
const session = useQuery({ queryKey: queryKeys.session, queryFn: () => call<Session>(sessionRoute) });
const me$ = useQuery({ queryKey: queryKeys.me, queryFn: () => call<Principal>(meRoute) });
// 运行态的两份数据只在运营中心取:设计中心不看运行数字(纪律),也不必替它要
const tasks = useGovTasks({ enabled: surface === 'ops' });
const objects = useGovObjects({ enabled: surface === 'ops' });
const queryClient = useQueryClient();
/** 登出后整体清空缓存 —— 治理台看到的是全组织数据,更不能留给下一个人 */
const logout = useMutation({
mutationFn: () => call<{ ok: boolean }>(logoutRoute),
onSuccess: () => {
queryClient.clear();
// 整页导航:SSE 连接、组件状态、路由位置一并归零 —— 登出不留残影
window.location.assign('/');
},
});
/*
* 登出(ADR-012):先整体清空缓存 —— 治理台看到的是全组织数据,更不能留给下一个人;
* 再走 Keycloak end-session 终结 SSO 会话(`/api/logout` 已退役 410,不再调)。
*/
const logout = () => {
queryClient.clear();
void kcLogout();
};
// 浏览器前进 / 后退即导航 —— 路由状态就是应用状态
useEffect(() => {
@@ -123,7 +123,7 @@ export function GovShell({ surface }: { surface: Surface }) {
const { zone, tab, stack } = place;
const meta = ZM[zone];
const top = stack[stack.length - 1];
const me = session.data?.staff;
const me = me$.data?.staff;
const other: Surface = surface === 'design' ? 'ops' : 'design';
/** 告警计数:异常工作流 + 非放行终态的任务 —— 只有运营中心管这个 */
@@ -141,11 +141,15 @@ export function GovShell({ surface }: { surface: Surface }) {
{ label: SURFACE_META[surface].brand, go: () => goZone(zone) },
];
crumbs.push({ label: meta.title, go: () => go({ zone, tab, stack: [] }) });
// 台账区的页签名就是域键 —— 静态表里查不到,呈现走人读短名
crumbs.push({
label: TABS[zone].find((t) => t.key === tab)?.label ?? (zone === 'ledgers' ? domainShort(effectiveTab) : tab),
go: () => go({ zone, tab, stack: [] }),
});
/* 页签级只在页签栏真出现时摆 —— 单页签的区页签栏收起(下文 g-tabs 同口径),
面包屑再摆一级同义页签名就是冗余(2026-08-25 用户定,活动区起) */
if (zone === 'ledgers' || TABS[zone].length > 1) {
// 台账区的页签名就是域键 —— 静态表里查不到,呈现走人读短名
crumbs.push({
label: TABS[zone].find((t) => t.key === tab)?.label ?? (zone === 'ledgers' ? domainShort(effectiveTab) : tab),
go: () => go({ zone, tab, stack: [] }),
});
}
stack.forEach((r, i) =>
crumbs.push({
label: routeLabel(r),
@@ -193,18 +197,20 @@ export function GovShell({ surface }: { surface: Surface }) {
告警<b>{alerts}</b>
</button>
)}
{/* 三面互跳:另一面按授权亮灯,工作台人人可回 */}
{canSeeSurface(session.data, other) && (
{/* 三面互跳:另一面与工作台都按 ends 亮灯(ADR-012,权限只到端) */}
{canSeeSurface(me$.data, other) && (
<a className="g-tbtn" href={SURFACE_META[other].path}>
<Ico name={other === 'design' ? 'route' : 'gauge'} s={13} />
{SURFACE_META[other].brand}
</a>
)}
<a className="g-tbtn" href="/">
<Ico name="list-checks" s={13} />
工作台
</a>
<Btn className="g-tbtn" onClick={() => logout.mutate()} busy={logout.isPending}>
{(me$.data?.ends ?? []).includes('workbench') && (
<a className="g-tbtn" href="/">
<Ico name="list-checks" s={13} />
工作台
</a>
)}
<Btn className="g-tbtn" onClick={logout}>
<Ico name="log-out" s={13} />
登出
</Btn>
@@ -323,10 +329,14 @@ function Detail({ route, nav }: { route: Route; nav: Nav }) {
return <TriggerPage id={route.id} />;
case 'act':
return <ActPage id={route.id} />;
case 'actd':
return <ActvPage id={route.id} />;
case 'tool':
return <ToolPage id={route.id} />;
case 'obj':
return <ObjPage id={route.id} />;
return <ObjPage id={route.id} nav={nav} />;
case 'row':
return <RowPage id={route.id} />;
case 'llm':
return <LlmPage id={route.id} />;
case 'chan':