This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/apps/web/src/features/gov-activity/internal/panel.tsx
T
ChaiHaoandClaude Opus 5 a9cd6d10c7 M6(2/n): 治理台九功能 + SSE 订阅层 + 读/治分权
治理台按 §9.1 拆成九个 gov-* feature,共用件落 shared/gov:
  · 外壳(左轨/顶栏/面包屑/页签/整页下钻)自原型 Governance.tsx 平移,
    DOM 与 g-* 类名原样;路由状态即应用状态,区/页签/下钻栈全编码进 hash 可深链
  · 编排画布 diagram-js 画布件 1:1 平移(flow-layout/flow-node-paint/FlowDiagram),
    新增 pkg-to-graph 把 ProcessPackage 转成画布图模型;统一报价 v1 全图 12 节点 15 边渲染通过
  · 后端补四路由:审批单 / 工作流下钻 / 通道实例 / 度量仪表

SSE 订阅层(§9.2):app 层单一连接,按信封目标失效缓存;
组件不再自行轮询(去掉 refetchInterval)。广播只说「哪个目标动了」,不带业务载荷,
且在投递已落库已送达之后才发 —— 不是第二条投递链路。

两处实质修正:
  · 治理路由一律要 govern → 读路由改要 read。客体三类 × 动作三种的分法就是为此:
    BD 有 task:read 能看在途、动不了控制事件;一律 govern 等于把三种动作压成一种
  · 控制事件/恢复/改派原记为 engine-transition(归机器产出)→ 新增 control 条目种类,
    归人裁决侧。归侧是按动作类型的纯推导,所以「谁干的」必须体现在 kind 上;
    否则人的叫停会被算进自主率的机器分子

判据 ⑩ 治理台一半跑通:在途监控 / 叫停 / 恢复,治理动作留痕可查。
无权查看不再画成空清单,按岗位说明缺哪一项。

gates: tsc / eslint / depcruise(286 模块)/ api-report / 360 测试 全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 03:28:40 +08:00

116 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* feature `gov-activity` —— 活动库:两维分类 × 包版本 × 资产版本
*
* 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移:
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
* 区首页只负责导航,治理动作一律落在下钻详情页的**就地确认带**(影响预览三必答)。
*/
import { Card, Chip, Clear, Entry, NList, NRow, Note, PageHead, QueryGate, St } from '@web-shared/gov/primitives.tsx';
import { PARA } from '@web-shared/gov/tone.ts';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import {
useGovActivities,
useGovMemory,
} from '@web-shared/gov/gov-api.ts';
export type HomeProps = { tab: string; nav: Nav };
export function LibraryHome({ nav }: HomeProps) {
const q = useGovActivities();
const activities = q.data?.activities ?? [];
return (
<QueryGate query={q}>
<>
<Note>
活动是任务的<b>定义</b>:编排引用一个活动,运行时就长出一个任务。业务分类 × 活动类型两维平级;
每活动两条版本序列——包版本走安装两审,资产版本走进化管线。
</Note>
<NList>
{activities.map((a) => {
const effective = a.assetVersions.filter((v) => v.effective);
return (
<NRow
key={a.activityId}
tone={PARA[a.paradigm].tone}
ico={PARA[a.paradigm].ico}
title={a.name}
sub={`${a.activityId} · ${a.category}`}
cols={[
{ k: '类型', v: PARA[a.paradigm].label },
{ k: '包版本', v: <span className="g-mono">{a.packageVersions.map((p) => p.version).join(', ')}</span> },
{ k: '资产生效版', v: <span className="g-mono">{effective.length}</span> },
]}
right={
<St tone={a.enabled ? 'ok' : 'mute'} ico={a.enabled ? 'circle-check' : 'power'}>
{a.enabled ? '已启用' : '已停用'}
</St>
}
onClick={() => nav.push({ k: 'act', id: a.activityId })}
/>
);
})}
</NList>
</>
</QueryGate>
);
}
/* ---------------- ⑥ 工具管理 ---------------- */
export function ActPage({ id, nav }: { id: string; nav: Nav }) {
const q = useGovActivities();
const memory = useGovMemory(id);
const a = (q.data?.activities ?? []).find((x) => x.activityId === id);
if (a === undefined) return <Note>活动不存在。</Note>;
return (
<>
<PageHead
ico={PARA[a.paradigm].ico}
tone={PARA[a.paradigm].tone}
kicker={`活动 · ${a.activityId}`}
title={a.name}
chips={
<>
<Chip tone={PARA[a.paradigm].tone}>{PARA[a.paradigm].label}</Chip>
<Chip ico="tag">{a.category}</Chip>
<Chip tone={a.enabled ? 'ok' : 'mute'}>{a.enabled ? '已启用' : '已停用'}</Chip>
</>
}
desc={PARA[a.paradigm].full + ' —— 活动是系统外开发、经此安装的外装件。'}
/>
<Card ico="package" title="包版本 · 安装两审">
<div className="g-rows">
{a.packageVersions.map((p) => (
<div key={p.version}>
<em>v{p.version}</em>
<b className="mono">
{p.state} · {p.installedAt.slice(0, 16).replace('T', ' ')}
</b>
</div>
))}
</div>
</Card>
<Card ico="trending-up" title="资产版本 · 进化管线">
<div className="g-rows">
{a.assetVersions.map((v, i) => (
<div key={i}>
<em>
{v.kind} v{v.version}
</em>
<b className="mono">{v.effective ? '生效中' : '历史'}</b>
</div>
))}
{a.assetVersions.length === 0 && <Clear>尚无资产版本。</Clear>}
</div>
</Card>
<Entry
ico="layers"
title="活动记忆"
desc={`四层条目 · 当前 ${(memory.data?.entries ?? []).length} 条`}
onClick={() => nav.jump('memory', 'layers')}
/>
</>
);
}