范式退役:活动只剩一种、只跑 Loop;审批活动可管;活动库重做

用户定的两件,一并落地。

**一、活动不再分 flow 和 loop —— 彻底退役 flow**
  · `flow-engine` 模块整个删除(runner 379 行 + step-sequence 184 行 + 测试);
  · `paradigm` 从清单身份、`activity_package`/`task` 两张表、任务卡与活动库一并退场;
    `businessModel` 不再是判别联合 —— 只有案例模型(CMMN + 说明书 + DMN)一形;
  · task 的引擎分派从「按 paradigm 取两个之一」收成**唯一的引擎**;
  · `activities/` 四个 flow 活动(A1 受理 / A4 定价 / A6 议价 / A7 归档)各写了一份
    案例模型:阶段照原来的步分,触达世界的项一律 manualActivationRule,
    能算的交 DMN(A1 的三分类 P-03、A4 的 P-01/P-02)不交 Agent;
  · 六个占位槽位的骨架从「机器步落台账 + 一张验收单」改成案例骨架,包版本 0.8.0 → 0.9.0。

**代价写在明处,不假装没有**:`turn-runner` 每一拍都要 `agent.decide` ——
环境里没有模型 key,案例活动一步也走不动。三套端到端(统一报价 / 新建流程包 / 测试台
Trace)整套跳过,文件头写明恢复条件:①有可用模型 ②走法改写成按 Enabled 集推进。
**不许把断言改松让它变绿** —— 绿得不诚实比红着更坏。debt.md 已记。

**二、审批活动可管**(新能力)
  · `sys.approval_template`:审批点**可复用的那一半** —— 审批人看哪几项、按什么名字看、
    怎么催、说什么话。谁来审不在里面(与活动点同口径,运营时轮值);驳回回哪个节点
    也不在里面(那是每张图自己的事,留在节点上);
  · 三条路由 + 引用反查:还被审批点引着就不许删(409),删了那些节点会**静默**退回旧行为;
  · 运行时 `basisOf`:引了模板就按模板筛与改名,没引即旧行为(载荷全摊开)。
    模板改了只影响新发出的审批单 —— 在途单的依据在发出那一刻已定形。

**三、活动库重做**:一区两格 —— 「活动(装进来的)」与「审批活动(配出来的)」,
都用流程卡那套两段式卡片(头 + 四格数据板),幽灵卡当入口(装入活动 / 新建审批活动);
「标准/案例」那一维随范式一起撤,业务分类成了唯一的一维,以行内小签呈现。

迁移 0020:两张表 drop paradigm、建 approval_template。已在开发库跑过并重播种。
实测(丁子乔账号):建「定价审批」模板 → 流程包 A5 属性里选得到 → 引用反查计数正确。
四闸绿(api:check 随本提交)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-29 23:53:51 +08:00
co-authored by Claude Opus 5
parent 18c3e3fd09
commit 1fd215cf40
88 changed files with 1319 additions and 2966 deletions
+8
View File
@@ -295,6 +295,14 @@
.g-fcard .menu button.bad { color: var(--danger); }
.g-fcard .menu button.bad:hover { background: #fdf2f2; }
.g-fcard .menu .q { padding: 3px 8px 5px; font-size: 11px; line-height: 1.5; color: var(--ink-3); }
/* 活动卡的分类签:压在卡右下角的一枚小字 —— 业务分类是活动库唯一的一维 */
/* 分类签:业务分类是活动库唯一的一维 —— 一枚行内小字,不另占一行 */
.g-fcard .tag { flex: none; padding: 2px 6px; border: 1px solid var(--line-soft); border-radius: 5px; font: 500 10px/1.4 var(--sans); color: var(--ink-4); }
.g-fcard .ico.apr { color: var(--warn); background: #fdf3e3; }
.g-fcard .mbtn.bad:hover { border-color: #e6c4c4; background: #fdf2f2; color: var(--danger); }
/* 审批依据:一行两格 + 删 */
.g-basis { display: flex; flex-direction: column; gap: 6px; }
.g-basis-r { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: 6px; }
/* 幽灵卡:新建住在货架里 —— 与真卡同宽同高,一眼看出「这儿还能再添一条」 */
.g-fcard.ghost { flex-direction: row; align-items: center; justify-content: center; gap: 7px; min-height: 102px; border-style: dashed; border-color: #c6d1de; background: transparent; box-shadow: none; color: var(--ink-4); font: 600 12.5px/1 var(--sans); cursor: pointer; }
.g-fcard.ghost:hover { border-color: var(--brand); background: var(--brand-wash); color: var(--brand-deep); box-shadow: none; transform: none; }
@@ -8,7 +8,8 @@
import { Ico } from '@web-shared/Ico.tsx';
import { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { installActivityRoute } from '@contracts';
import type { ApprovalTemplate } from '@contracts';
import { deleteApprovalTemplateRoute, installActivityRoute, upsertApprovalTemplateRoute } from '@contracts';
import { ApiCallError, call } from '@web-shared/typed-fetch.ts';
import {
Acts,
@@ -20,7 +21,7 @@ import {
PageHead,
Prop,
QueryGate,
St,
Sec,
} from '@web-shared/gov/primitives.tsx';
/** 装入回参 —— 与 `installActivityRoute.output` 同形 */
@@ -36,72 +37,264 @@ type InstallReply = {
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';
import { useGovActivities, useGovApprovalTemplates } from '@web-shared/gov/gov-api.ts';
import type { GovApprovalTemplates } from '@web-shared/gov/gov-api.ts';
export type HomeProps = { tab: string; nav: Nav };
export function LibraryHome({ nav }: HomeProps) {
const q = useGovActivities();
const templates = useGovApprovalTemplates();
const activities = q.data?.activities ?? [];
const [installing, setInstalling] = useState(false);
return (
<QueryGate query={q}>
<>
<Note>
活动是任务的<b>定义</b>:编排引用一个活动,运行时就长出一个任务。业务分类 × 活动类型两维平级;
每活动两条版本序列——包版本走安装两审,资产版本走进化管线。
<b>审批点与网关不在这里</b>:它们是流程层的系统内置要素,零开发、不入库。
</Note>
<InstallCard />
{activities.length === 0 ? (
<Clear>货架是空的 —— 装一个活动进来,编排里才引用得到它。</Clear>
) : (
{/*
* 一区两格:**装进来的**(活动,系统外开发、经安装通道入库)与
* **配出来的**(审批活动,治理台里配的模板)。
* 「标准 / 案例」那一维 2026-07-29 撤了:活动不再分范式,业务分类是唯一的一维。
*/}
<Sec ico="boxes" title="活动" extra={`${activities.length} 个 · 装进来的`}>
<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} />
<div key={a.activityId} className={'g-fcard' + (a.enabled ? '' : ' off')}>
<button className="hit" aria-label={`打开 ${a.name}`} onClick={() => nav.push({ k: 'act', id: a.activityId })} />
<div className="hd">
<span className="ico">
<Ico name="boxes" s={15} />
</span>
<span className="g-tile-t">
<div className="ttl">
<b>{a.name}</b>
<em>{a.activityId}</em>
</div>
<span className="tag">{a.category}</span>
<span className={'stx' + (a.enabled ? ' on' : '')}>{a.enabled ? '已上架' : '已停用'}</span>
</div>
<div className="plate">
<span>
<b>{a.contract.inputFields.length}</b>
<i>入参</i>
</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>
<span>
<b>{a.contract.outputPorts.length}</b>
<i>出口</i>
</span>
<span>
<b>{latest?.version ?? '—'}</b>
<i>包版本</i>
</span>
<span>
<b>{effective}</b>
<i>资产生效</i>
</span>
</div>
</div>
);
})}
{/* 幽灵卡:装入通道 —— 活动是系统外开发的,这里只收清单与产物 */}
<button className="g-fcard ghost" onClick={() => setInstalling(!installing)}>
<Ico name="upload" s={18} />
{installing ? '收起装入' : '装入活动'}
</button>
</div>
)}
</Sec>
{installing && <InstallCard />}
<ApprovalTemplates data={templates.data?.templates ?? []} />
</>
</QueryGate>
);
}
/* ---------------- 审批活动:治理台里配出来的那一类 ---------------- */
/**
* 审批活动 —— **配出来的活动**(2026-07-29 用户定:审批活动要能管)。
*
* 它与装进来的活动是两种东西:审批零开发,系统内置;可配的只是**可复用的那一半** ——
* 审批人看哪几项、按什么名字看、怎么催、说什么话。
* 剩下不可复用的那半(驳回回哪个节点)留在编排的节点上,那是那张图自己的事;
* 谁来审也不在这里 —— 与活动点同一口径,运营时按组轮值。
*/
function ApprovalTemplates({ data }: { data: GovApprovalTemplates['templates'] }) {
const queryClient = useQueryClient();
const [editing, setEditing] = useState<ApprovalTemplate | null>(null);
const [err, setErr] = useState<string | undefined>(undefined);
const invalidate = () => {
void queryClient.invalidateQueries({ queryKey: ['gov', 'approval-templates'] });
void queryClient.invalidateQueries({ queryKey: ['gov', 'process'] });
};
const save = useMutation({
mutationFn: (template: ApprovalTemplate) =>
call<{ template: ApprovalTemplate }>(upsertApprovalTemplateRoute, { body: { template } }),
onSuccess: () => {
invalidate();
setEditing(null);
setErr(undefined);
},
onError: (e: unknown) => setErr((e as { message?: string }).message ?? '存不下'),
});
const drop = useMutation({
mutationFn: (id: string) => call<{ ok: boolean }>(deleteApprovalTemplateRoute, { params: { id } }),
onSuccess: () => {
invalidate();
setErr(undefined);
},
onError: (e: unknown) => setErr((e as { message?: string }).message ?? '删不掉'),
});
return (
<Sec ico="shield-check" title="审批活动" extra={`${data.length} 张模板 · 配出来的`}>
{err !== undefined && <Note tone="warn">{err}</Note>}
{editing !== null && (
<TemplateForm
draft={editing}
pending={save.isPending}
onCancel={() => setEditing(null)}
onSave={(t) => save.mutate(t)}
/>
)}
<div className="g-shelf">
{data.map((t) => (
<div key={t.id} className="g-fcard">
<div className="hd">
<span className="ico apr">
<Ico name="shield-check" s={15} />
</span>
<div className="ttl">
<b>{t.name}</b>
<em>{t.id}</em>
</div>
<button className="mbtn" aria-label="编辑" onClick={() => setEditing({ ...t })}>
<Ico name="pen-line" s={13} />
</button>
<button
className="mbtn bad"
aria-label="删除"
disabled={drop.isPending}
onClick={() => drop.mutate(t.id)}
>
<Ico name="trash-2" s={13} />
</button>
</div>
<div className="plate">
<span>
<b>{t.basis.length === 0 ? '全部' : t.basis.length}</b>
<i>审批依据</i>
</span>
<span>
<b>{t.nudge === undefined ? '—' : t.nudge.deadline}</b>
<i>时限</i>
</span>
<span>
<b>{t.usedBy.length}</b>
<i>被引用</i>
</span>
</div>
</div>
))}
<button
className="g-fcard ghost"
onClick={() => setEditing({ id: '', name: '', basis: [] })}
>
<Ico name="plus" s={18} />
新建审批活动
</button>
</div>
</Sec>
);
}
/** 模板表单 —— 依据一行一条:载荷字段 → 卡面显示名 */
function TemplateForm({
draft,
pending,
onCancel,
onSave,
}: {
draft: ApprovalTemplate;
pending: boolean;
onCancel: () => void;
onSave: (t: ApprovalTemplate) => void;
}) {
const [t, setT] = useState<ApprovalTemplate>(draft);
const idOk = /^[a-z][a-z0-9-]{1,39}$/.test(t.id);
return (
<Card ico="shield-check" title={draft.id === '' ? '新建审批活动' : `改 ${draft.name}`}>
<div className="g-newflow">
<Field k="名称" hint="人读的名字,如「定价审批」">
<input className="g-input" value={t.name} placeholder="如:定价审批" onChange={(e) => setT({ ...t, name: e.target.value })} />
</Field>
<Field k="标识" hint="编排的审批点按它引用 —— 小写字母开头,只许小写 / 数字 / 连字符">
<input
className="g-input mono"
value={t.id}
disabled={draft.id !== ''}
placeholder="如:pricing-approval"
onChange={(e) => setT({ ...t, id: e.target.value.trim() })}
/>
</Field>
<Field k="审批依据" hint="审批人卡面上看哪几项 —— 留空即把载荷全摊开">
<div className="g-basis">
{t.basis.map((b, i) => (
<div key={i} className="g-basis-r">
<input
className="g-input mono"
value={b.field}
placeholder="载荷字段"
onChange={(e) =>
setT({ ...t, basis: t.basis.map((x, j) => (i === j ? { ...x, field: e.target.value.trim() } : x)) })
}
/>
<input
className="g-input"
value={b.label}
placeholder="卡面显示名"
onChange={(e) => setT({ ...t, basis: t.basis.map((x, j) => (i === j ? { ...x, label: e.target.value } : x)) })}
/>
<button className="g-ver-act t-bad" onClick={() => setT({ ...t, basis: t.basis.filter((_, j) => j !== i) })}>
删
</button>
</div>
))}
<button className="g-ver-act" onClick={() => setT({ ...t, basis: [...t.basis, { field: '', label: '' }] })}>
<Ico name="plus" s={11} /> 加一条
</button>
</div>
</Field>
<Field k="委托消息" hint="给审批人的一句话,随审批单发出">
<input
className="g-input"
value={t.message ?? ''}
placeholder="如:请核对毛利率与偏离度后裁决"
onChange={(e) => setT({ ...t, message: e.target.value === '' ? undefined : e.target.value })}
/>
</Field>
{t.id !== '' && !idOk && <Note tone="warn">标识不合规:小写字母开头,只许小写、数字、连字符。</Note>}
<Acts
align="end"
items={[
{ label: '取消', ico: 'x', onClick: onCancel },
{
label: pending ? '保存中…' : '保存',
ico: 'send',
primary: true,
disabled: pending || !idOk || t.name.trim() === '' || t.basis.some((b) => b.field === '' || b.label === ''),
onClick: () => onSave({ ...t, name: t.name.trim() }),
},
]}
/>
</div>
</Card>
);
}
/* ---------------- ⑥ 工具管理 ---------------- */
export function ActPage({ id }: { id: string }) {
@@ -111,18 +304,17 @@ export function ActPage({ id }: { id: string }) {
return (
<>
<PageHead
ico={PARA[a.paradigm].ico}
tone={PARA[a.paradigm].tone}
ico="boxes"
tone="brand"
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 + ' —— 活动是系统外开发、经此安装的外装件。'}
desc="活动是系统外开发、经安装通道入库的外装件 —— 一件事怎么办,由它的案例模型说。"
/>
{/*
契约面 —— 开发这个活动要照着的东西:吃什么、吐什么、用谁的工具、动哪些域对象。
@@ -44,7 +44,7 @@ import {
St,
} from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import { useGovActivities, useGovPackages, useGovPkgReports } from '@web-shared/gov/gov-api.ts';
import { useGovActivities, useGovApprovalTemplates, useGovPackages, useGovPkgReports } from '@web-shared/gov/gov-api.ts';
import type { GovActivities } from '@web-shared/gov/gov-api.ts';
import { FlowFigureBlock } from '@web-shared/gov/flow/FlowFigure.tsx';
import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
@@ -374,6 +374,7 @@ export function PkgPage({ id, nav }: { id: string; nav: Nav }) {
const flowId = id.split('@')[0] ?? id;
const q = useGovPackages();
const acts = useGovActivities();
const approvalTpl = useGovApprovalTemplates();
const versions = (q.data?.packages ?? [])
.filter((p) => p.id === flowId)
@@ -400,6 +401,7 @@ export function PkgPage({ id, nav }: { id: string; nav: Nav }) {
activeVersion={active?.version}
nav={nav}
activities={acts.data?.activities ?? []}
approvalTemplates={approvalTpl.data?.templates ?? []}
nextVersion={Math.max(...versions.map((x) => x.version)) + 1}
closedOf={(v) => reports.data?.get(`${flowId}@${v}`)?.closed}
onPick={(v) => setPinned(v)}
@@ -412,7 +414,6 @@ export function PkgPage({ id, nav }: { id: string; nav: Nav }) {
type ActivityRow = {
activityId: string;
name: string;
paradigm: string;
packageVersions: ReadonlyArray<{ version: string }>;
assetVersions: ReadonlyArray<{ version: number; effective: boolean }>;
contract: GovActivities['activities'][number]['contract'];
@@ -424,6 +425,7 @@ function FlowLab({
activeVersion,
nav,
activities,
approvalTemplates,
nextVersion,
closedOf,
onPick,
@@ -434,6 +436,8 @@ function FlowLab({
activeVersion: number | undefined;
nav: Nav;
activities: readonly ActivityRow[];
/** 审批模板一览 —— 审批点属性里选一张 */
approvalTemplates: readonly { id: string; name: string }[];
nextVersion: number;
/** 该版本闭合与否(undefined = 报告还没回来)—— 激活按钮据此灰显 */
closedOf: (version: number) => boolean | undefined;
@@ -1107,6 +1111,34 @@ function FlowLab({
</div>
</div>
)}
{node.kind === 'activity' && node.flavor === 'approval' && (
<div className="g-tbrow">
{/*
* 审批模板 = 从活动库链接一张**审批活动** —— 审批人看哪几项、怎么催、说什么。
* 不选也能跑(旧行为:审批卡把载荷全摊开);谁来审仍是运营时按组轮值。
*/}
<em>审批模板 · 链接自活动库</em>
<span className="v col">
<select
className="g-tbsel"
value={node.templateId ?? ''}
onChange={(e) =>
patch((n) => ({ ...n, templateId: e.target.value === '' ? undefined : e.target.value }))
}
>
<option value="">不引模板 —— 审批卡把载荷全摊开</option>
{approvalTemplates.map((t) => (
<option key={t.id} value={t.id}>
{t.name} · {t.id}
</option>
))}
</select>
<button className="g-tblink" onClick={() => nav.jump('library', 'shelf')}>
去活动库配审批活动 <Ico name="arrow-right" s={11} />
</button>
</span>
</div>
)}
{node.kind === 'activity' && node.flavor !== 'approval' && (
<div className="g-tbrow">
{/* 实现 = **从活动库链接一个活动**:节点不含实现,链接才算装载(激活判据之二) */}
@@ -21,7 +21,7 @@ import { Ico } from '@web-shared/Ico.tsx';
import { call } from '@web-shared/typed-fetch.ts';
import { Ava, Card, Chip, Clear, Entry, Facts, Kpi, NList, NRow, Note, PageHead, QueryGate, Sec, St } from '@web-shared/gov/primitives.tsx';
import type { ActSpec } from '@web-shared/gov/primitives.tsx';
import { APR_ST, PARA, RUNTIME_ST, TERMINAL_ST, WF_ST } from '@web-shared/gov/tone.ts';
import { APR_ST, RUNTIME_ST, TERMINAL_ST, WF_ST } from '@web-shared/gov/tone.ts';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import { FlowFigure } from '@web-shared/gov/flow/FlowFigure.tsx';
import {
@@ -174,8 +174,8 @@ function RuntimeHome({ tab, nav }: HomeProps) {
return (
<NRow
key={t.id}
tone={PARA[t.paradigm].tone}
ico={PARA[t.paradigm].ico}
tone="brand"
ico="repeat"
title={t.title}
sub={`${t.id.slice(0, 8)} · 活动 ${t.activityId}`}
cols={[
@@ -399,7 +399,7 @@ export function TaskPage({ id, nav }: { id: string; nav: Nav }) {
return (
<>
<PageHead
ico={PARA[task.paradigm].ico}
ico="repeat"
tone={st.tone}
kicker={`任务 · ${task.id.slice(0, 8)}`}
title={task.title}
@@ -408,13 +408,10 @@ export function TaskPage({ id, nav }: { id: string; nav: Nav }) {
<Chip tone={st.tone} ico={st.ico}>
{label}
</Chip>
<Chip tone={PARA[task.paradigm].tone} ico={PARA[task.paradigm].ico}>
{PARA[task.paradigm].label}
</Chip>
<Chip ico="user-round">{task.ownerName}</Chip>
</>
}
desc={PARA[task.paradigm].full + ' —— 治理不改定义:控制只作用于这一次运行。'}
desc="活动的一次运行 —— 治理不改定义:控制只作用于这一次运行。"
acts={[
controlAct('pause', false),
controlAct('halt', true),
@@ -440,7 +437,6 @@ export function TaskPage({ id, nav }: { id: string; nav: Nav }) {
items={[
{ k: '步数', v: `${task.usedSteps}/${task.capSteps}`, ratio: task.usedSteps / Math.max(task.capSteps, 1) },
{ k: '活动', v: task.activityId },
{ k: '范式', v: PARA[task.paradigm].label },
{ k: '起始', v: task.startedAt.slice(0, 16).replace('T', ' ') },
]}
/>
@@ -26,6 +26,8 @@ export interface FlowNode {
* (蛇形折行与尺寸计算),不表示它在模型上属于活动。
*/
flavor?: "case" | "approval" | undefined;
/** 仅审批点:引哪张审批模板(活动库 · 审批活动)—— 不引即旧行为:审批卡把载荷全摊开。 */
templateId?: string | undefined;
/** Case 节点引用的库案例 id(cases[].id):L2 只是引用,下钻编辑共享库案例。 */
caseRef?: string;
label: string;
@@ -67,6 +67,12 @@ export function mergeGraphIntoPackage(
// 审批人不入编排(运营时分配):功能属性只剩驳回走向 / 催办 / 委托消息
// 驳回走向是声明式功能属性,画布不改它 —— 没有旧声明就先「终止」,激活前在属性里补
reject: priorAp?.reject ?? { kind: 'terminate' as const },
// 审批模板:属性卡刚选的优先,其次旧声明 —— 画布上看不见它,但存盘不能丢
...(n.templateId !== undefined && n.templateId !== ''
? { templateId: n.templateId }
: priorAp?.templateId === undefined
? {}
: { templateId: priorAp.templateId }),
...(priorAp?.nudge === undefined ? {} : { nudge: priorAp.nudge }),
...(priorAp?.message === undefined ? {} : { message: priorAp.message }),
};
+1 -1
View File
@@ -91,7 +91,7 @@ export function packageToGraph(pkg: ProcessPackage, marks: readonly InstanceMark
};
if (n.kind === 'approval') {
// approval **不是一种活动**:它挂在 kind:"activity" 下仅因共用同一套方框几何
return { ...base, flavor: 'approval' };
return { ...base, flavor: 'approval', ...(n.templateId === undefined ? {} : { templateId: n.templateId }) };
}
return {
...base,
+10
View File
@@ -25,12 +25,14 @@ import type {
GovWorkflowOutput,
ProcessPackage,
} from '@contracts';
import type { ApprovalTemplate } from '@contracts';
import {
browseLedgerRoute,
govMetricsRoute,
govTaskLogRoute,
govWorkflowRoute,
listActivitiesRoute,
listApprovalTemplatesRoute,
listActivityMemoryRoute,
listApprovalsRoute,
listChannelsRoute,
@@ -118,6 +120,14 @@ export const useGovTriggers = (): UseQueryResult<GovTriggersOutput> =>
export const useGovActivities = (): UseQueryResult<GovActivities> =>
useQuery({ queryKey: ['gov', 'activities'], queryFn: () => call<GovActivities>(listActivitiesRoute) });
/** 审批模板(审批活动)—— 活动库的第二格、编排里审批点的选项 */
export type GovApprovalTemplates = { templates: ReadonlyArray<ApprovalTemplate & { usedBy: ReadonlyArray<{ packageId: string; nodeId: string }> }> };
export const useGovApprovalTemplates = (): UseQueryResult<GovApprovalTemplates> =>
useQuery({
queryKey: ['gov', 'approval-templates'],
queryFn: () => call<GovApprovalTemplates>(listApprovalTemplatesRoute),
});
export const useGovTools = (): UseQueryResult<GovTools> =>
useQuery({ queryKey: ['gov', 'tools'], queryFn: () => call<GovTools>(listToolsRoute) });
+1 -1
View File
@@ -119,7 +119,7 @@ export const ZM: Readonly<Record<GovZone, { title: string; desc: string; accent:
title: '活动库',
icon: 'boxes',
accent: 'linear-gradient(140deg,#0aa294,#0a6a64)',
desc: '已安装活动的资产货架:活动是任务的定义——编排引用一个活动,运行时就长出一个任务。审批点与网关是系统内置要素,零开发、不入库。业务分类 × 活动类型两维,每活动两条版本序列。',
desc: '一区两格:装进来的活动(系统外开发、经安装通道入库,一个活动 = 一类任务的定义)与配出来的审批活动(审批零开发,这里配的是模板:审批人看什么、怎么催、说什么)。活动不再分标准/案例——业务分类是唯一的一维;每活动两条版本序列:包版本走安装两审,资产版本走进化管线。',
},
tools: {
title: '工具箱',
+1 -6
View File
@@ -2,7 +2,7 @@
* 状态 → 色调 / 图标字典 —— 自原型 `Governance.tsx` 第 92–116 行平移。
* 原型用中文状态名做键;我们的状态名是契约里的枚举,故键换成枚举、标签仍取契约的 `*_LABEL`。
*/
import type { Paradigm, TaskRuntimeState, TerminalState, WorkflowState } from '@contracts';
import type { TaskRuntimeState, TerminalState, WorkflowState } from '@contracts';
export type ToneIco = { tone: string; ico: string };
@@ -32,11 +32,6 @@ export const TERMINAL_ST: Readonly<Record<TerminalState, ToneIco>> = {
terminated: { tone: 'mute', ico: 'power' },
};
export const PARA: Readonly<Record<Paradigm, ToneIco & { label: string; full: string }>> = {
loop: { tone: 'collab', ico: 'repeat', label: '案例 · loop', full: '临场演绎,调度权归 AI' },
flow: { tone: 'auto', ico: 'git-commit', label: '标准 · flow', full: '照谱执行,调度权归系统' },
};
export const APR_ST: Readonly<Record<'pending' | 'passed' | 'rejected', ToneIco & { label: string }>> = {
pending: { tone: 'warn', ico: 'hourglass', label: '待审批' },
passed: { tone: 'ok', ico: 'circle-check', label: '已通过' },