diff --git a/apps/web/src/app/main.tsx b/apps/web/src/app/main.tsx index f5b6006..c0ce80e 100644 --- a/apps/web/src/app/main.tsx +++ b/apps/web/src/app/main.tsx @@ -1,13 +1,14 @@ /** * apps/web 唯一组装层(编码规范 §9):样式装载、Provider、鉴权壳、SSE 连接。 * - * **两台面各自装自己的皮肤。** + * **三面各自装自己的皮肤。** * 两份原型本是两个独立应用,各自只加载自己那两份 CSS: * 工作台 = `proto.vendor.css`(原型皮肤)+ `app.css`(补充,作用域 `.proto-root`) - * 治理台 = `theme.css`(令牌 + 页面基座)+ `console.css`(gov-* 组件) + * 治理面 = `theme.css`(令牌 + 页面基座)+ `console.css`(gov-* 组件) * 两份基座都写了 `body { min-width / background }` 这类**全局**规则,谁后加载谁赢 —— * 四份一起塞进同一个文档,后加载的治理台基座会把工作台的壳整个盖掉(实测:布局全崩)。 * 所以按台面**分别装载**,这也正是 1:1 平移该有的样子:每台面拿到的样式集与它的原型一致。 + * 设计中心与运营中心是治理的两个台面,共用同一套皮肤,只是区表与首页不同。 */ import { StrictMode, useEffect } from 'react'; import { createRoot } from 'react-dom/client'; @@ -21,24 +22,40 @@ import { LoginPage } from '@features/auth'; import { ApiCallError, call } from '@web-shared/typed-fetch.ts'; import { queryClient, queryKeys } from './query.ts'; import { applyTabIdentity } from './tab-identity.ts'; +import type { TabSurface } from './tab-identity.ts'; +import { SURFACE_META, legacyGovTarget } from '@web-shared/gov/surfaces.ts'; +import type { Surface } from '@web-shared/gov/surfaces.ts'; const container = document.getElementById('root'); if (!container) throw new Error('#root 缺失'); -/** 路径或 hash 决定进哪台 —— 路由状态即应用状态,可深链(§9.2) */ -const isGovernance = - window.location.pathname.startsWith('/gov') || window.location.hash.startsWith('#/gov'); +/* + * 旧 `/gov` 链接一条不死:带着深链原样改写到新台面(replace —— 不在历史里留一个回不去的地址)。 + * 治理中心一分为二后,`/gov` 只剩这一个职责。 + */ +const legacy = window.location.pathname.startsWith('/gov') + ? (legacyGovTarget(window.location.hash) ?? `${SURFACE_META.ops.path}#/ops/cockpit/dash`) + : undefined; +if (legacy !== undefined) window.location.replace(legacy); + +/** 路径决定进哪一面 —— 路由状态即应用状态,可深链(§9.2) */ +const surface: TabSurface = window.location.pathname.startsWith(SURFACE_META.design.path) + ? 'design' + : window.location.pathname.startsWith(SURFACE_META.ops.path) + ? 'ops' + : 'desk'; +const govSurface: Surface | undefined = surface === 'desk' ? undefined : surface; // 网页标签的名与章 —— 样式装载前就挂上,标签从第一帧起就叫对名字 -applyTabIdentity(isGovernance); +applyTabIdentity(surface); /* * 样式先装、再渲染:CSS 落地后才挂 DOM,避免无样式闪烁。 - * 台面切换靠整页导航(两台面的 body 基座互斥,不做客户端软切换)。 - * 登录页两台面共用,故恒装 —— 它通篇作用域在 `.jc-login`,不与任一基座相争。 + * 台面切换靠整页导航(工作台与治理面的 body 基座互斥,不做客户端软切换)。 + * 登录页三面共用,故恒装 —— 它通篇作用域在 `.jc-login`,不与任一基座相争。 */ await import('./styles/login.css'); -if (isGovernance) { +if (govSurface !== undefined) { await import('./styles/theme.css'); await import('./styles/console.css'); } else { @@ -79,7 +96,7 @@ function Gate() { return client.setQueryData(queryKeys.session, s)} />; } - return isGovernance ? ( + return govSurface !== undefined ? ( ) : ( // .proto-root 是工作台补充样式的作用域(原型 app.css 通篇以它起头); diff --git a/apps/web/src/app/tab-identity.ts b/apps/web/src/app/tab-identity.ts index 5dfe430..da2ab7b 100644 --- a/apps/web/src/app/tab-identity.ts +++ b/apps/web/src/app/tab-identity.ts @@ -1,9 +1,10 @@ /** - * 网页标签的名与章 —— 两台面各有一套,多标签并开时一眼可辨。 + * 网页标签的名与章 —— 三面各有一套,多标签并开时一眼可辨「我在定义、在监控、还是在干活」。 * * 章随台面的**皮肤基调**走,不另造视觉: - * 工作台 = 品牌蓝渐变圆角方 + 白色机器人(与左轨品牌章、登录页方章同源); - * 治理中心 = 深色底(治理台顶栏的那种深)+ 盾形勾(管的是规则与放行)。 + * 工作台 = 品牌蓝渐变圆角方 + 白色机器人(与左轨品牌章、登录页方章同源); + * 设计中心 = 深靛底 + 路网(定义企业的流转 —— 与流程包区的靛色同源); + * 运营中心 = 深色底 + 仪表(看企业今天跑得怎样 —— 驾驶舱的那支指针)。 * SVG 以 data: URI 内联 —— 不添静态资产,构建零配置。 */ @@ -24,17 +25,39 @@ const DESK_ICON = ` `; -const GOV_ICON = ` - - - - +/** 设计中心:深靛底 + 路网 —— 线接着点,正是「连线即映射」 */ +const DESIGN_ICON = ` + + + + + `; -/** 进哪台就挂哪套 —— 登录页也按 URL 归台,标签在登录前就叫对名字 */ -export function applyTabIdentity(isGovernance: boolean): void { - document.title = isGovernance ? 'JCAI 治理中心' : 'JCAI 工作台'; +/** 运营中心:深色底 + 仪表 —— 指针指着「今天跑得怎样」 */ +const OPS_ICON = ` + + + + + + +`; + +/** 三面各一套章名 —— `desk` 是工作台,另两个是治理的两个台面 */ +export type TabSurface = 'desk' | 'design' | 'ops'; + +const IDENTITY: Readonly> = { + desk: { title: 'JCAI 工作台', icon: DESK_ICON }, + design: { title: 'JCAI 设计中心', icon: DESIGN_ICON }, + ops: { title: 'JCAI 运营中心', icon: OPS_ICON }, +}; + +/** 进哪面就挂哪套 —— 登录页也按 URL 归面,标签在登录前就叫对名字 */ +export function applyTabIdentity(surface: TabSurface): void { + const { title, icon } = IDENTITY[surface]; + document.title = title; let link = document.querySelector('link[rel="icon"]'); if (link === null) { @@ -43,5 +66,5 @@ export function applyTabIdentity(isGovernance: boolean): void { document.head.appendChild(link); } link.type = 'image/svg+xml'; - link.href = 'data:image/svg+xml;utf8,' + encodeURIComponent(isGovernance ? GOV_ICON : DESK_ICON); + link.href = 'data:image/svg+xml;utf8,' + encodeURIComponent(icon); }