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 Fable 5 21b66567bd MVP底座⑦:活动库卡片化 —— 六槽位卡
卡上只报查得到的事实(范式/分类/包版本/资产生效版/上架):
清单里没有「实现到没到位」的可靠依据,不在卡上编一个章 —— 槽位状态记在 debt.md。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-28 02:04:11 +08:00

136 lines
5.2 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 { Ico } from '@web-shared/Ico.tsx';
import { Card, Chip, Clear, Entry, 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>:编排引用一个活动,运行时就长出一个任务。业务分类 × 活动类型两维平级;
每活动两条版本序列——包版本走安装两审,资产版本走进化管线。
<b>审批点与网关不在这里</b>:它们是流程层的系统内置要素,零开发、不入库。
</Note>
{activities.length === 0 ? (
<Clear>货架是空的 —— 装一个活动进来,编排里才引用得到它。</Clear>
) : (
<div className="g-shelf">
{activities.map((a) => {
const para = PARA[a.paradigm];
const latest = a.packageVersions[a.packageVersions.length - 1];
const effective = a.assetVersions.filter((v) => v.effective).length;
return (
<button
key={a.activityId}
className={'g-tile t-' + para.tone}
onClick={() => nav.push({ k: 'act', id: a.activityId })}
>
<span className="g-tile-h">
<span className="g-tile-i">
<Ico name={para.ico} s={16} />
</span>
<span className="g-tile-t">
<b>{a.name}</b>
<em>{a.activityId}</em>
</span>
<Chip tone={para.tone}>{para.label}</Chip>
</span>
<span className="g-tile-m">
<Chip ico="boxes">{a.category}</Chip>
<Chip ico="git-commit">包 v{latest?.version ?? '—'}</Chip>
</span>
<span className="g-tile-f">
{/*
只报查得到的事实:资产生效版数与上架状态。
「实现到没到位」这一层,清单里没有可靠依据 —— 不在卡上编一个章。
*/}
<St tone={a.enabled ? 'ok' : 'mute'} ico={a.enabled ? 'circle-check' : 'power'}>
{a.enabled ? '已上架' : '已停用'}
</St>
<span className="r">资产生效 {effective}</span>
</span>
</button>
);
})}
</div>
)}
</>
</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('knowledge', 'layers')}
/>
</>
);
}