Archived
范式退役:活动只剩一种、只跑 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:
@@ -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 }),
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) });
|
||||
|
||||
|
||||
@@ -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: '工具箱',
|
||||
|
||||
@@ -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: '已通过' },
|
||||
|
||||
Reference in New Issue
Block a user