/** * 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 { useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { installActivityRoute } from '@contracts'; import { ApiCallError, call } from '@web-shared/typed-fetch.ts'; import { Acts, Card, Chip, Clear, Field, Note, PageHead, Prop, QueryGate, St, } from '@web-shared/gov/primitives.tsx'; /** 装入回参 —— 与 `installActivityRoute.output` 同形 */ type InstallReply = { accepted: boolean; activityId: string; packageVersion: string; staticFindings: ReadonlyArray<{ code: string; severity: string; message: string }>; replay: { oldPassStillPass: boolean; checked: number; skippedNoHistory: boolean; regressions: ReadonlyArray<{ taskId: string; detail: string }>; }; }; import { PARA } from '@web-shared/gov/tone.ts'; import type { Nav } from '@web-shared/gov/gov-route.ts'; import { useGovActivities } 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 ( <> 活动是任务的定义:编排引用一个活动,运行时就长出一个任务。业务分类 × 活动类型两维平级; 每活动两条版本序列——包版本走安装两审,资产版本走进化管线。 审批点与网关不在这里:它们是流程层的系统内置要素,零开发、不入库。 {activities.length === 0 ? ( 货架是空的 —— 装一个活动进来,编排里才引用得到它。 ) : (
{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 ( ); })}
)}
); } /* ---------------- ⑥ 工具管理 ---------------- */ export function ActPage({ id }: { id: string }) { const q = useGovActivities(); const a = (q.data?.activities ?? []).find((x) => x.activityId === id); if (a === undefined) return 活动不存在。; return ( <> {PARA[a.paradigm].label} {a.category} {a.enabled ? '已启用' : '已停用'} } desc={PARA[a.paradigm].full + ' —— 活动是系统外开发、经此安装的外装件。'} /> {/* 契约面 —— 开发这个活动要照着的东西:吃什么、吐什么、用谁的工具、动哪些域对象。 它排在版本谱系前面:先看清契约,再看装了哪几版。 */} {a.contract.inputFields.length === 0 ? ( 不吃结构化入参 —— 只凭委托消息启动。 ) : (
{a.contract.inputFields.map((f) => (
{f.name} {f.required && 必填} {f.type}
))}
)}
{a.contract.outputPorts.map((port) => (
{port.name} {port.fields.join(' · ') || '(无字段)'}
))} {a.contract.outputPorts.length === 0 && 没有声明输出口 —— 下游无从衔接,发布会被拦。}
工具 {a.contract.tools.join(' · ') || '(不调工具)'}
模型 {a.contract.models.join(' · ') || '(不调模型)'}
岗位 {a.contract.roles.join(' · ') || '(未声明)'}
域对象 {a.contract.objects.join(' · ') || '(不读写台账)'}
{a.packageVersions.map((p) => (
v{p.version} {p.state} · {p.installedAt.slice(0, 16).replace('T', ' ')}
))}
{a.assetVersions.map((v, i) => (
{v.kind} v{v.version} {v.effective ? '生效中' : '历史'}
))} {a.assetVersions.length === 0 && 尚无资产版本。}
); } /* ---------------- 装入 / 换版(安装通道) ---------------- */ /** * 开发完一个活动,从这里装进来 —— **无代码通道**(ADR-004):交的是清单 + .js 产物,都是数据。 * * 两审在服务端跑:静态审读清单,回放审重演历史(首装无历史即免)。 * 恒为新增、版本并存:装入不覆盖旧版本,在途任务按其启动时版本跑完。 */ function InstallCard() { const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const [manifestText, setManifestText] = useState(''); const [artifact, setArtifact] = useState(''); const install = useMutation({ mutationFn: () => call(installActivityRoute, { body: { manifest: JSON.parse(manifestText) as unknown, artifact }, }), onSuccess: (out) => { if (out.accepted) { void queryClient.invalidateQueries({ queryKey: ['gov', 'activities'] }); setOpen(false); } }, }); const pick = (setter: (text: string) => void) => async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file !== undefined) setter(await file.text()); }; /** 两审未过的结论:412 走的是 ApiCallError,详情在 detail 里 */ const rejected = install.error instanceof ApiCallError && install.error.status === 412 ? (install.error.detail as InstallReply | undefined) : install.data?.accepted === false ? install.data : undefined; if (!open) { return ( setOpen(true)}> 装一个活动 } > ); } return (