Archived
三面拆分①:surface 配置与区表 —— 归属/亮灯/旧链改写皆纯函数先测后写
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -12,13 +12,14 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { logoutRoute } from '@contracts';
|
import { logoutRoute } from '@contracts';
|
||||||
import { call } from '@web-shared/typed-fetch.ts';
|
import { call } from '@web-shared/typed-fetch.ts';
|
||||||
import { Ico } from '@web-shared/Ico.tsx';
|
import { Ico } from '@web-shared/Ico.tsx';
|
||||||
|
import { Note } from '@web-shared/gov/primitives.tsx';
|
||||||
import { TABS, ZM, ZONES, routeLabel } from '@web-shared/gov/gov-route.ts';
|
import { TABS, ZM, ZONES, routeLabel } from '@web-shared/gov/gov-route.ts';
|
||||||
import type { GovZone, Nav, Route } from '@web-shared/gov/gov-route.ts';
|
import type { GovZone, Nav, Route } from '@web-shared/gov/gov-route.ts';
|
||||||
import { useGovStaff, useGovTasks } from '@web-shared/gov/gov-api.ts';
|
import { useGovStaff, useGovTasks } from '@web-shared/gov/gov-api.ts';
|
||||||
import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
|
import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
|
||||||
import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff';
|
import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff';
|
||||||
import { EdgesPage, PkgPage, WorkflowsHome } from '@features/gov-process';
|
import { EdgesPage, PkgPage, WorkflowsHome } from '@features/gov-process';
|
||||||
import { TriggerPage, TriggersHome } from '@features/gov-trigger';
|
import { TriggerPage } from '@features/gov-trigger';
|
||||||
import { ActPage, LibraryHome } from '@features/gov-activity';
|
import { ActPage, LibraryHome } from '@features/gov-activity';
|
||||||
import { ToolPage, ToolsHome } from '@features/gov-tool';
|
import { ToolPage, ToolsHome } from '@features/gov-tool';
|
||||||
import { DataHome, ObjPage } from '@features/gov-data';
|
import { DataHome, ObjPage } from '@features/gov-data';
|
||||||
@@ -34,7 +35,8 @@ function readPlace(): Place {
|
|||||||
const parts = raw.split('/').filter((s) => s !== '');
|
const parts = raw.split('/').filter((s) => s !== '');
|
||||||
const zone = (ZONES.find((z) => z.key === parts[1])?.key ?? 'tasks') as GovZone;
|
const zone = (ZONES.find((z) => z.key === parts[1])?.key ?? 'tasks') as GovZone;
|
||||||
const tabs = TABS[zone];
|
const tabs = TABS[zone];
|
||||||
const tab = tabs.find((t) => t.key === parts[2])?.key ?? tabs[0]!.key;
|
// 台账区的页签是数据域(动态生成),静态表里没有 —— 认不出就原样收下,别兜到不存在的首签
|
||||||
|
const tab = tabs.find((t) => t.key === parts[2])?.key ?? tabs[0]?.key ?? (parts[2] ?? '');
|
||||||
const stack: Route[] = [];
|
const stack: Route[] = [];
|
||||||
for (const seg of parts.slice(3)) {
|
for (const seg of parts.slice(3)) {
|
||||||
const at = seg.indexOf(':');
|
const at = seg.indexOf(':');
|
||||||
@@ -83,7 +85,7 @@ export function GovShell() {
|
|||||||
const p = readPlace();
|
const p = readPlace();
|
||||||
go({ ...p, stack: p.stack.slice(0, -1) });
|
go({ ...p, stack: p.stack.slice(0, -1) });
|
||||||
}, [go]);
|
}, [go]);
|
||||||
const goZone = useCallback((z: GovZone) => go({ zone: z, tab: TABS[z][0]!.key, stack: [] }), [go]);
|
const goZone = useCallback((z: GovZone) => go({ zone: z, tab: TABS[z][0]?.key ?? '', stack: [] }), [go]);
|
||||||
const jump = useCallback((z: GovZone, tab: string, r?: Route) => go({ zone: z, tab, stack: r ? [r] : [] }), [go]);
|
const jump = useCallback((z: GovZone, tab: string, r?: Route) => go({ zone: z, tab, stack: r ? [r] : [] }), [go]);
|
||||||
const nav: Nav = { push, jump };
|
const nav: Nav = { push, jump };
|
||||||
|
|
||||||
@@ -218,16 +220,35 @@ export function GovShell() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 企业蓝图 —— Task 6 接真;在那之前先给设计者一句「这里将有什么」 */
|
||||||
|
function BlueprintPlaceholder() {
|
||||||
|
return (
|
||||||
|
<Note>
|
||||||
|
这里将把四库串成一条因果链:流程包(战略)→ 活动库(方法)→ 工具箱(能力)→ 数据域(对象),
|
||||||
|
并把「哪里还没定义完」聚成一张待补清单。先从左轨进各库编排。
|
||||||
|
</Note>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 台账 —— Task 10 接真;导航项已随数据域长出来了,内容随后 */
|
||||||
|
function LedgersPlaceholder() {
|
||||||
|
return <Note>这里将按域列出全部台账对象、行数据与版本。域的结构在设计中心 · 数据域维护。</Note>;
|
||||||
|
}
|
||||||
|
|
||||||
function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) {
|
function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) {
|
||||||
const P = {
|
const P = {
|
||||||
tasks: TasksHome,
|
// 设计中心
|
||||||
people: PeopleHome,
|
blueprint: BlueprintPlaceholder,
|
||||||
workflows: WorkflowsHome,
|
workflows: WorkflowsHome,
|
||||||
triggers: TriggersHome,
|
|
||||||
library: LibraryHome,
|
library: LibraryHome,
|
||||||
tools: ToolsHome,
|
tools: ToolsHome,
|
||||||
data: DataHome,
|
data: DataHome,
|
||||||
memory: MemoryHome,
|
// 运营中心
|
||||||
|
cockpit: MemoryHome,
|
||||||
|
tasks: TasksHome,
|
||||||
|
people: PeopleHome,
|
||||||
|
ledgers: LedgersPlaceholder,
|
||||||
|
knowledge: MemoryHome,
|
||||||
system: SystemHome,
|
system: SystemHome,
|
||||||
}[zone];
|
}[zone];
|
||||||
return <P tab={tab} nav={nav} />;
|
return <P tab={tab} nav={nav} />;
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function ActPage({ id, nav }: { id: string; nav: Nav }) {
|
|||||||
ico="layers"
|
ico="layers"
|
||||||
title="活动记忆"
|
title="活动记忆"
|
||||||
desc={`四层条目 · 当前 ${(memory.data?.entries ?? []).length} 条`}
|
desc={`四层条目 · 当前 ${(memory.data?.entries ?? []).length} 条`}
|
||||||
onClick={() => nav.jump('memory', 'layers')}
|
onClick={() => nav.jump('knowledge', 'layers')}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
|
|||||||
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
||||||
import type { FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
import type { FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
||||||
import type { SpPosition } from '@web-shared/gov/flow/support.tsx';
|
import type { SpPosition } from '@web-shared/gov/flow/support.tsx';
|
||||||
|
import { TriggersHome } from '@features/gov-trigger';
|
||||||
|
|
||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
@@ -75,6 +76,12 @@ function edgeCheckOf(pkg: ProcessPackage, report: ClosureReport | undefined) {
|
|||||||
/* ---------------- ③ 工作流管理 ---------------- */
|
/* ---------------- ③ 工作流管理 ---------------- */
|
||||||
|
|
||||||
export function WorkflowsHome({ tab, nav }: HomeProps) {
|
export function WorkflowsHome({ tab, nav }: HomeProps) {
|
||||||
|
// 调起规则 = 触发器的**定义面**:谁调起这条流,与流程包一处维护(启停在运营中心)
|
||||||
|
if (tab === 'rules') return <TriggersHome tab={tab} nav={nav} />;
|
||||||
|
return <PackagesHome tab={tab} nav={nav} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PackagesHome({ tab, nav }: HomeProps) {
|
||||||
const q = useGovPackages();
|
const q = useGovPackages();
|
||||||
const packages = q.data?.packages ?? [];
|
const packages = q.data?.packages ?? [];
|
||||||
const reports = usePkgReports(packages);
|
const reports = usePkgReports(packages);
|
||||||
@@ -835,7 +842,7 @@ function Orchestrator({
|
|||||||
title={t.name}
|
title={t.name}
|
||||||
sub={t.spec.kind === 'event' ? t.spec.rule : t.spec.kind === 'registration' ? TRIGGER_KIND_LABEL.registration : TRIGGER_KIND_LABEL.periodic}
|
sub={t.spec.kind === 'event' ? t.spec.rule : t.spec.kind === 'registration' ? TRIGGER_KIND_LABEL.registration : TRIGGER_KIND_LABEL.periodic}
|
||||||
cols={[{ k: '状态', v: t.enabled ? '已启用' : '已停用' }]}
|
cols={[{ k: '状态', v: t.enabled ? '已启用' : '已停用' }]}
|
||||||
onClick={() => nav.jump('triggers', 'rules', { k: 'trigger', id: t.id })}
|
onClick={() => nav.jump('workflows', 'rules', { k: 'trigger', id: t.id })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NList>
|
</NList>
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* 三面骨架的唯一事实源 —— 区归属、亮灯、旧链接改写。
|
||||||
|
*
|
||||||
|
* 这里守的是「配置自洽」:每个区恰属一面(不重不漏);亮灯只看该面功能;旧深链一条不死。
|
||||||
|
* 三者都是纯函数,壳层只消费 —— 配置错了在这里就红,不必等到点开界面才发现。
|
||||||
|
*/
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import type { Session } from '@contracts';
|
||||||
|
import { sessionSchema } from '@contracts';
|
||||||
|
import { GOV_ZONES } from '../gov-route.ts';
|
||||||
|
import { SURFACE_ZONES, canSeeSurface, legacyGovTarget, surfaceOfZone } from '../surfaces.ts';
|
||||||
|
|
||||||
|
/** 经 schema 构造合法会话 —— §12.5 禁 `as unknown as`,测试同样遵守 */
|
||||||
|
const sessionWith = (fns: readonly string[]): Session =>
|
||||||
|
sessionSchema.parse({
|
||||||
|
staff: { id: 's-1', account: 'a', name: 'n', positionId: 'p-1', state: 'active' },
|
||||||
|
position: { id: 'p-1', code: 'PM', name: '项目经理' },
|
||||||
|
grants: fns.map((fn) => ({
|
||||||
|
positionId: 'p-1',
|
||||||
|
object: { kind: 'governance', function: fn },
|
||||||
|
action: 'read',
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SURFACE_ZONES —— 每个区恰属一面', () => {
|
||||||
|
it('两面之和即全部区,不重不漏', () => {
|
||||||
|
const all = [...SURFACE_ZONES.design, ...SURFACE_ZONES.ops];
|
||||||
|
expect([...all].sort()).toEqual([...GOV_ZONES].sort());
|
||||||
|
expect(new Set(all).size).toBe(all.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('设计面没有任何运行态区', () => {
|
||||||
|
for (const zone of ['tasks', 'cockpit', 'ledgers'] as const) {
|
||||||
|
expect(SURFACE_ZONES.design).not.toContain(zone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('surfaceOfZone 与区表一致', () => {
|
||||||
|
expect(surfaceOfZone('workflows')).toBe('design');
|
||||||
|
expect(surfaceOfZone('ledgers')).toBe('ops');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('canSeeSurface —— 入口按授权亮灯', () => {
|
||||||
|
it('持有该面任一功能的授权即亮', () => {
|
||||||
|
expect(canSeeSurface(sessionWith(['process']), 'design')).toBe(true);
|
||||||
|
expect(canSeeSurface(sessionWith(['task']), 'ops')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('只有另一面的授权不亮', () => {
|
||||||
|
expect(canSeeSurface(sessionWith(['task']), 'design')).toBe(false);
|
||||||
|
expect(canSeeSurface(sessionWith(['activity']), 'ops')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trigger 与 data 两面都算 —— 定义归设计,处置与行数据归运营', () => {
|
||||||
|
expect(canSeeSurface(sessionWith(['trigger']), 'design')).toBe(true);
|
||||||
|
expect(canSeeSurface(sessionWith(['trigger']), 'ops')).toBe(true);
|
||||||
|
expect(canSeeSurface(sessionWith(['data']), 'design')).toBe(true);
|
||||||
|
expect(canSeeSurface(sessionWith(['data']), 'ops')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('无会话不亮;无治理授权不亮', () => {
|
||||||
|
expect(canSeeSurface(undefined, 'design')).toBe(false);
|
||||||
|
expect(canSeeSurface(sessionWith([]), 'ops')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('legacyGovTarget —— 旧 #/gov 深链一条不死', () => {
|
||||||
|
it('设计态区改写到 /design,深链原样带走', () => {
|
||||||
|
expect(legacyGovTarget('#/gov/workflows/pkgs/pkg:unified-quote%404')).toBe(
|
||||||
|
'/design#/design/workflows/pkgs/pkg:unified-quote%404',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('运行态区改写到 /ops', () => {
|
||||||
|
expect(legacyGovTarget('#/gov/tasks/board')).toBe('/ops#/ops/tasks/board');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('已删的区落到对应新家', () => {
|
||||||
|
expect(legacyGovTarget('#/gov/triggers/rules')).toBe('/design#/design/workflows/rules');
|
||||||
|
expect(legacyGovTarget('#/gov/memory/metrics')).toBe('/ops#/ops/cockpit/dash');
|
||||||
|
expect(legacyGovTarget('#/gov/memory/layers')).toBe('/ops#/ops/knowledge/layers');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('裸 #/gov 落运营首页;非 gov 哈希不管', () => {
|
||||||
|
expect(legacyGovTarget('#/gov')).toBe('/ops#/ops/cockpit/dash');
|
||||||
|
expect(legacyGovTarget('')).toBeUndefined();
|
||||||
|
expect(legacyGovTarget('#/whatever')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,24 +22,69 @@ export type Route =
|
|||||||
| { k: 'llm'; id: string }
|
| { k: 'llm'; id: string }
|
||||||
| { k: 'chan'; id: string };
|
| { k: 'chan'; id: string };
|
||||||
|
|
||||||
/** 九功能 —— 每一项对应架构中的一件,治理台由此覆盖全部五层 */
|
/**
|
||||||
export const GOV_ZONES = ['tasks', 'people', 'workflows', 'triggers', 'library', 'tools', 'data', 'memory', 'system'] as const;
|
* 十一区 —— 分属两个台面(见 `surfaces.ts`):
|
||||||
|
* 设计中心定义企业(战略 → 方法 → 能力 → 对象),运营中心看企业跑。
|
||||||
|
* 触发器不再独立成区:定义并入 workflows,启停并入 tasks;
|
||||||
|
* 记忆一分为二:度量升为 cockpit 首页,活动记忆归 knowledge。
|
||||||
|
*/
|
||||||
|
export const GOV_ZONES = [
|
||||||
|
// 设计中心
|
||||||
|
'blueprint',
|
||||||
|
'workflows',
|
||||||
|
'library',
|
||||||
|
'tools',
|
||||||
|
'data',
|
||||||
|
// 运营中心
|
||||||
|
'cockpit',
|
||||||
|
'tasks',
|
||||||
|
'people',
|
||||||
|
'ledgers',
|
||||||
|
'knowledge',
|
||||||
|
'system',
|
||||||
|
] as const;
|
||||||
export type GovZone = (typeof GOV_ZONES)[number];
|
export type GovZone = (typeof GOV_ZONES)[number];
|
||||||
|
|
||||||
export const ZONES: ReadonlyArray<{ key: GovZone; label: string; icon: string }> = [
|
export const ZONES: ReadonlyArray<{ key: GovZone; label: string; icon: string }> = [
|
||||||
|
{ key: 'blueprint', label: '蓝图', icon: 'route' },
|
||||||
|
{ key: 'workflows', label: '流程包', icon: 'workflow' },
|
||||||
|
{ key: 'library', label: '活动库', icon: 'boxes' },
|
||||||
|
{ key: 'tools', label: '工具箱', icon: 'wrench' },
|
||||||
|
{ key: 'data', label: '数据域', icon: 'database' },
|
||||||
|
{ key: 'cockpit', label: '驾驶舱', icon: 'gauge' },
|
||||||
{ key: 'tasks', label: '任务', icon: 'activity' },
|
{ key: 'tasks', label: '任务', icon: 'activity' },
|
||||||
{ key: 'people', label: '员工', icon: 'users' },
|
{ key: 'people', label: '员工', icon: 'users' },
|
||||||
{ key: 'workflows', label: '工作流', icon: 'workflow' },
|
{ key: 'ledgers', label: '台账', icon: 'database' },
|
||||||
{ key: 'triggers', label: '触发器', icon: 'zap' },
|
{ key: 'knowledge', label: '知识库', icon: 'layers' },
|
||||||
{ key: 'library', label: '活动库', icon: 'boxes' },
|
|
||||||
{ key: 'tools', label: '工具', icon: 'wrench' },
|
|
||||||
{ key: 'data', label: '数据', icon: 'database' },
|
|
||||||
{ key: 'memory', label: '记忆', icon: 'layers' },
|
|
||||||
{ key: 'system', label: '系统', icon: 'settings' },
|
{ key: 'system', label: '系统', icon: 'settings' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 区元信息:标题 / 定位说明 / 主色 —— 说明文取自两卷规范,不是装饰 */
|
/** 区元信息:标题 / 定位说明 / 主色 —— 说明文取自两卷规范,不是装饰 */
|
||||||
export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent: string; icon: string }>> = {
|
export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent: string; icon: string }>> = {
|
||||||
|
blueprint: {
|
||||||
|
title: '企业蓝图',
|
||||||
|
icon: 'route',
|
||||||
|
accent: 'linear-gradient(140deg,#4954e6,#232b6e)',
|
||||||
|
desc: '战略 → 方法 → 能力 → 对象:流程包决定企业以哪些流转运转,活动库决定每件事怎么干,工具箱决定系统能对世界做什么,数据域决定企业运营什么。四库的健康度与待补清单聚在这一屏——设计者看的是定义完不完整,不是今天忙不忙。',
|
||||||
|
},
|
||||||
|
cockpit: {
|
||||||
|
title: '经营驾驶舱',
|
||||||
|
icon: 'gauge',
|
||||||
|
accent: 'linear-gradient(140deg,#12b886,#0b7a5c)',
|
||||||
|
desc: '看企业今天跑得怎样:度量八指标(自主率与纠正率成对读,复合为有效自主率)+ 异常面(异常工作流、需处置件数)。指标零埋点——全部由留痕投影而来,点数字直达处置页。',
|
||||||
|
},
|
||||||
|
ledgers: {
|
||||||
|
title: '台账管理',
|
||||||
|
icon: 'database',
|
||||||
|
accent: 'linear-gradient(140deg,#245bdb,#16307a)',
|
||||||
|
desc: '行数据 · 版本 · 引用——域的 schema 在设计中心改,这里做运营动作。导航按数据域动态生成:设计侧一发布新域,运营侧自动长出一位。',
|
||||||
|
},
|
||||||
|
knowledge: {
|
||||||
|
title: '知识库',
|
||||||
|
icon: 'layers',
|
||||||
|
accent: 'linear-gradient(140deg,#f14ba9,#c92d86)',
|
||||||
|
desc: '活动记忆四层 + 技能资产 + 候选增量的回放与放行。记忆的终点是成为模型——候选增量必经回放与人放行,这里是那道人工放行口。',
|
||||||
|
},
|
||||||
tasks: {
|
tasks: {
|
||||||
title: '任务管理',
|
title: '任务管理',
|
||||||
icon: 'activity',
|
icon: 'activity',
|
||||||
@@ -53,40 +98,28 @@ export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent:
|
|||||||
desc: '员工负载、岗位与授权。权限按岗位授予:客体三类 × 动作三种;停用前置检查——在途任务与待答卡须先改派完毕。',
|
desc: '员工负载、岗位与授权。权限按岗位授予:客体三类 × 动作三种;停用前置检查——在途任务与待答卡须先改派完毕。',
|
||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
title: '工作流管理',
|
title: '流程包管理',
|
||||||
icon: 'workflow',
|
icon: 'workflow',
|
||||||
accent: 'linear-gradient(140deg,#4954e6,#3a44c9)',
|
accent: 'linear-gradient(140deg,#4954e6,#3a44c9)',
|
||||||
desc: '设计态:流程包的五要素编排(活动 · 审批点 · 网关 · 触发器 · 流程包)与版本。编排即定契约——连线即映射、画图即校验,边不闭合不得发布。工作流 = 流程包的运行时实例。',
|
desc: '战略——企业以哪些流转运转。五要素编排(活动 · 审批点 · 网关 · 触发器 · 流程包)与版本:编排即定契约,连线即映射、画图即校验,边不闭合不得发布。调起规则(触发器的定义)也在本区:谁调起这条流,与流程包一处维护;启停在运营中心。',
|
||||||
},
|
|
||||||
triggers: {
|
|
||||||
title: '触发器配置',
|
|
||||||
icon: 'zap',
|
|
||||||
accent: 'linear-gradient(140deg,#ff8800,#de7802)',
|
|
||||||
desc: '起单时机的规则清单:周期 / 事件 / 登记三类。触发器与流程包平级——不入包结构、不随包版本,增删启停不改任何版本。',
|
|
||||||
},
|
},
|
||||||
library: {
|
library: {
|
||||||
title: '活动库管理',
|
title: '活动库',
|
||||||
icon: 'boxes',
|
icon: 'boxes',
|
||||||
accent: 'linear-gradient(140deg,#0aa294,#0a6a64)',
|
accent: 'linear-gradient(140deg,#0aa294,#0a6a64)',
|
||||||
desc: '已安装活动的资产货架:活动是任务的定义——编排引用一个活动,运行时就长出一个任务。审批点与网关是系统内置要素,零开发、不入库。业务分类 × 活动类型两维,每活动两条版本序列。',
|
desc: '已安装活动的资产货架:活动是任务的定义——编排引用一个活动,运行时就长出一个任务。审批点与网关是系统内置要素,零开发、不入库。业务分类 × 活动类型两维,每活动两条版本序列。',
|
||||||
},
|
},
|
||||||
tools: {
|
tools: {
|
||||||
title: '工具管理',
|
title: '工具箱',
|
||||||
icon: 'wrench',
|
icon: 'wrench',
|
||||||
accent: 'linear-gradient(140deg,#7f3bf5,#5b21cf)',
|
accent: 'linear-gradient(140deg,#7f3bf5,#5b21cf)',
|
||||||
desc: '能力目录:功能工具按效应等级分列——读类过程中自由调用,写类只登记动作意向、放行后落地。密钥只存治理台。',
|
desc: '能力——系统能对世界做什么。功能工具按效应等级分列:读类过程中自由调用,写类只登记动作意向、放行后落地。密钥只存治理台。',
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: '数据管理',
|
title: '数据域',
|
||||||
icon: 'database',
|
icon: 'database',
|
||||||
accent: 'linear-gradient(140deg,#0e9fd8,#245bdb)',
|
accent: 'linear-gradient(140deg,#0e9fd8,#245bdb)',
|
||||||
desc: '域 × 对象的承载全景:声明物化(最大化合并、只增不破坏)与调用接入(事实源在外)。数据可以自生长,权力不可以自生长。',
|
desc: '对象——企业运营什么。域 × 对象的结构定义:声明物化(最大化合并、只增不破坏)与调用接入(事实源在外)。数据可以自生长,权力不可以自生长。行数据不在这里看——那在运营中心的台账。',
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
title: '记忆管理',
|
|
||||||
icon: 'layers',
|
|
||||||
accent: 'linear-gradient(140deg,#f14ba9,#c92d86)',
|
|
||||||
desc: '度量仪表 + 进化队列:自主率与纠正率成对读,复合为有效自主率。候选增量必经回放与人放行才成为模型。',
|
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
title: '系统管理',
|
title: '系统管理',
|
||||||
@@ -96,31 +129,36 @@ export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent:
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 区内页签:一页一个维度 —— 不把四种维度摞在同一屏 */
|
/**
|
||||||
|
* 区内页签:一页一个维度 —— 不把四种维度摞在同一屏。
|
||||||
|
*
|
||||||
|
* `ledgers` 的页签是**数据域**,随发布动态生成(壳层运行时铺开),故此表留空。
|
||||||
|
*/
|
||||||
export const TABS: Readonly<Record<GovZone, ReadonlyArray<{ key: string; label: string; ico: string }>>> = {
|
export const TABS: Readonly<Record<GovZone, ReadonlyArray<{ key: string; label: string; ico: string }>>> = {
|
||||||
|
blueprint: [{ key: 'map', label: '企业蓝图', ico: 'route' }],
|
||||||
|
workflows: [
|
||||||
|
{ key: 'overview', label: '总览', ico: 'gauge' },
|
||||||
|
{ key: 'pkgs', label: '流程包', ico: 'boxes' },
|
||||||
|
{ key: 'rules', label: '调起规则', ico: 'zap' },
|
||||||
|
{ key: 'todo', label: '需处置', ico: 'flag' },
|
||||||
|
],
|
||||||
|
library: [{ key: 'shelf', label: '活动货架', ico: 'boxes' }],
|
||||||
|
tools: [{ key: 'catalog', label: '能力目录', ico: 'wrench' }],
|
||||||
|
data: [{ key: 'domains', label: '数据对象', ico: 'database' }],
|
||||||
|
cockpit: [{ key: 'dash', label: '驾驶舱', ico: 'gauge' }],
|
||||||
tasks: [
|
tasks: [
|
||||||
{ key: 'board', label: '运行看板', ico: 'activity' },
|
{ key: 'board', label: '运行看板', ico: 'activity' },
|
||||||
{ key: 'list', label: '任务', ico: 'list-checks' },
|
{ key: 'list', label: '任务', ico: 'list-checks' },
|
||||||
{ key: 'apr', label: '审批单', ico: 'shield-check' },
|
{ key: 'apr', label: '审批单', ico: 'shield-check' },
|
||||||
|
{ key: 'trg', label: '起单开关', ico: 'zap' },
|
||||||
{ key: 'todo', label: '需处置', ico: 'flag' },
|
{ key: 'todo', label: '需处置', ico: 'flag' },
|
||||||
],
|
],
|
||||||
people: [
|
people: [
|
||||||
{ key: 'emp', label: '员工', ico: 'users' },
|
{ key: 'emp', label: '员工', ico: 'users' },
|
||||||
{ key: 'roles', label: '岗位授权', ico: 'key' },
|
{ key: 'roles', label: '岗位授权', ico: 'key' },
|
||||||
],
|
],
|
||||||
workflows: [
|
ledgers: [],
|
||||||
{ key: 'overview', label: '总览', ico: 'gauge' },
|
knowledge: [{ key: 'layers', label: '活动记忆', ico: 'layers' }],
|
||||||
{ key: 'pkgs', label: '流程包', ico: 'boxes' },
|
|
||||||
{ key: 'todo', label: '需处置', ico: 'flag' },
|
|
||||||
],
|
|
||||||
triggers: [{ key: 'rules', label: '起单规则', ico: 'zap' }],
|
|
||||||
library: [{ key: 'shelf', label: '活动货架', ico: 'boxes' }],
|
|
||||||
tools: [{ key: 'catalog', label: '能力目录', ico: 'wrench' }],
|
|
||||||
data: [{ key: 'domains', label: '数据对象', ico: 'database' }],
|
|
||||||
memory: [
|
|
||||||
{ key: 'metrics', label: '度量仪表', ico: 'gauge' },
|
|
||||||
{ key: 'layers', label: '活动记忆', ico: 'layers' },
|
|
||||||
],
|
|
||||||
system: [
|
system: [
|
||||||
{ key: 'llm', label: 'LLM 实例', ico: 'cpu' },
|
{ key: 'llm', label: 'LLM 实例', ico: 'cpu' },
|
||||||
{ key: 'chan', label: '通道实例', ico: 'mail' },
|
{ key: 'chan', label: '通道实例', ico: 'mail' },
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 三面骨架的唯一事实源(docs/产品设计.md §1 / §6)。
|
||||||
|
*
|
||||||
|
* 产品分三面:设计中心定义企业、运营中心看企业跑、工作台在企业里干活。
|
||||||
|
* 区归属、入口亮灯、旧链接改写都从这里出 —— 壳层只消费,不各写各的。
|
||||||
|
*/
|
||||||
|
import type { GovernanceFunction, Session } from '@contracts';
|
||||||
|
import { GOV_ZONES } from './gov-route.ts';
|
||||||
|
import type { GovZone } from './gov-route.ts';
|
||||||
|
|
||||||
|
/** 两个治理台面 —— 工作台是第三面,但它不吃这套区/页签骨架 */
|
||||||
|
export type Surface = 'design' | 'ops';
|
||||||
|
|
||||||
|
export const SURFACE_META: Readonly<Record<Surface, { path: string; brand: string; searchHint: string }>> = {
|
||||||
|
design: {
|
||||||
|
path: '/design',
|
||||||
|
brand: '设计中心',
|
||||||
|
searchHint: '检索定义:流程包 / 活动 / 工具 / 数据域 / 调起规则…',
|
||||||
|
},
|
||||||
|
ops: {
|
||||||
|
path: '/ops',
|
||||||
|
brand: '运营中心',
|
||||||
|
searchHint: '检索运行:工作流 / 任务 / 员工 / 台账…',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 区归属:设计五区 + 运营六区,恰好铺满 `GOV_ZONES`(由测试守住) */
|
||||||
|
export const SURFACE_ZONES: Readonly<Record<Surface, readonly GovZone[]>> = {
|
||||||
|
design: ['blueprint', 'workflows', 'library', 'tools', 'data'],
|
||||||
|
ops: ['cockpit', 'tasks', 'people', 'ledgers', 'knowledge', 'system'],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮灯功能集 —— 授权模型不动(仍是九功能 × 读/写/治),这里只说「哪个功能属哪一面」。
|
||||||
|
*
|
||||||
|
* `trigger` 与 `data` 两面都算:触发器**定义归设计、启停归运营**;
|
||||||
|
* 数据**结构归设计、行数据归运营**。同一功能的两种动作各归其位,不拆授权。
|
||||||
|
*/
|
||||||
|
export const SURFACE_FNS: Readonly<Record<Surface, readonly GovernanceFunction[]>> = {
|
||||||
|
design: ['process', 'activity', 'tool', 'data', 'trigger'],
|
||||||
|
ops: ['task', 'staff', 'memory', 'system', 'trigger', 'data'],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 台面入口是否亮灯。
|
||||||
|
*
|
||||||
|
* 判据是「持有该面任一功能的**任意**授权」—— 动作蕴含 govern ⊃ write ⊃ read,
|
||||||
|
* 有 grant 即至少可读,入口就该看得见;进去之后动作仍逐功能校验。
|
||||||
|
*/
|
||||||
|
export function canSeeSurface(session: Session | undefined, surface: Surface): boolean {
|
||||||
|
if (session === undefined) return false;
|
||||||
|
const fns = SURFACE_FNS[surface];
|
||||||
|
return session.grants.some((g) => g.object.kind === 'governance' && fns.includes(g.object.function));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function surfaceOfZone(zone: GovZone): Surface {
|
||||||
|
return SURFACE_ZONES.design.includes(zone) ? 'design' : 'ops';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGovZone(value: string): value is GovZone {
|
||||||
|
return (GOV_ZONES as readonly string[]).includes(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已删区的改嫁表:键是 `区` 或 `区/页签`,值是新台面下的 `台面/区/页签`。
|
||||||
|
* 精确到页签的先匹配 —— 记忆的两个旧页签各归其位(度量升驾驶舱,活动记忆归知识库)。
|
||||||
|
*/
|
||||||
|
const LEGACY_MOVES: Readonly<Record<string, string>> = {
|
||||||
|
triggers: 'design/workflows/rules',
|
||||||
|
memory: 'ops/cockpit/dash',
|
||||||
|
'memory/layers': 'ops/knowledge/layers',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 运营首页 —— 认不出的旧链接一律落这儿,不给白屏 */
|
||||||
|
const OPS_HOME = 'ops/cockpit/dash';
|
||||||
|
|
||||||
|
function toUrl(place: string): string {
|
||||||
|
const head = place.split('/')[0];
|
||||||
|
const surface: Surface = head === 'design' ? 'design' : 'ops';
|
||||||
|
return `${SURFACE_META[surface].path}#/${place}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旧 `#/gov/<区>/…` 深链 → 新台面的完整 URL(pathname + hash)。
|
||||||
|
* 非 gov 哈希返回 `undefined`(不是我们的事,别乱改人家的地址)。
|
||||||
|
*/
|
||||||
|
export function legacyGovTarget(hash: string): string | undefined {
|
||||||
|
const parts = hash
|
||||||
|
.replace(/^#\/?/, '')
|
||||||
|
.split('/')
|
||||||
|
.filter((s) => s !== '');
|
||||||
|
if (parts[0] !== 'gov') return undefined;
|
||||||
|
|
||||||
|
const zone = parts[1];
|
||||||
|
if (zone === undefined) return toUrl(OPS_HOME);
|
||||||
|
|
||||||
|
const moved = LEGACY_MOVES[`${zone}/${parts[2] ?? ''}`] ?? LEGACY_MOVES[zone];
|
||||||
|
if (moved !== undefined) return toUrl(moved);
|
||||||
|
if (!isGovZone(zone)) return toUrl(OPS_HOME);
|
||||||
|
|
||||||
|
// 仍在的区:原样带着页签与下钻栈搬到新台面
|
||||||
|
return toUrl([surfaceOfZone(zone), ...parts.slice(1)].join('/'));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user