流程包去掉发布机制 —— 严格检查 + 保存即生效,生效由运行时决定
用户要的是运行时机制:新建 + 编辑,存下去就生效,旧任务走完旧版本, 新任务调用时才加载新编排。所以「草稿 → 发布」两态整套去掉。 ## 闸从「发布」挪到「保存」 `savePackage` 现在先跑闭合校验,不闭合抛 `NotClosedError`(路由 412 带报告)—— **一个字节都不落库**。存进来的每一版都能起单,所以不需要「已发布」这个标记。 `publish` 用例、路由、`publishPackage` 存储方法一并删掉。 实测:把进 A6 的边映射清空后保存 → 412,逐条点名「边 e-g3-a6 未完成载荷映射; 节点「报价议价」的必填字段未映射:rows」。 ## 保存恒新增版本号,服务端说了算 这不是可选项,是「旧任务走完旧版本」的前提:工作流按 (包id, 版本) 去读定义, 就地覆盖 v1 就等于改掉所有在途 v1 任务正在读的编排 —— 它们会中途换了规则却毫不知情。 版本号由用例层算(当前最大 + 1),**不采信入参**。实测:入参故意填 version: 999, 落的是 v2。store 层因此改成**只插不改**:撞键宁可让唯一约束报错,也不静默覆盖。 起单取版从「最新已发布」改为「最新一版」(retired 除外);触发器的 requiredInputs 同口径。 实测:改完描述存成 v2 后,新起的单用 v2,早先那单仍挂 v1。 ## 迁移:旧状态值不归一就是一读就炸 状态枚举从 draft/published/retired 收成 active/retired。列上和 definition 这个 jsonb 里 各存着一份 state,两处都要改 —— 不改,`processPackageSchema.parse` 读一行就抛。 这不是开发期小麻烦,任何已部署实例都会中招。加了迁移 0012。 ## 界面:恒为 0 的假指标撤掉,不换个说法留着 - 编排面板:「已发布/草稿」芯片 → 「生效中/历史版本」(真正的区别是哪一版会被新起单取到); 发布按钮撤掉,保存按钮改成「保存并生效(存为 vN)」并在确认卡里说清影响面 - 历史版本只读,但就地指路:「去 v(N-1) 改(生效中)」,不让人自己找 - 蓝图面:「草稿 N · 未闭合 M」两个计数在新机制下**结构上恒为 0**(不闭合存不进来), 换成「共 N 个版本,保存即生效」 - 驾驶舱:「未闭合草稿」卡整张撤掉 —— 同理恒为 0。天然的第三张是「死路」 (route-dead-end 事件),但那需要一条按事件类型聚合的路由,没有之前不摆(同死信卡的规矩) ## 顺手修:种子的幂等判断本来是坏的 比对用 `JSON.stringify` 认键序,而 jsonb 从 PG 读回来键是重排过的 —— 永远判不等, 每播一次种就多存一版一模一样的定义(实测一口气堆到 v3)。改成递归按键名排序后比对。 连播三次,只第一次存。 ## 契约变更(破坏性,一步到位) `publish` 删除、`NotPublishableError` → `NotClosedError`、`publishable` → `closed`、 状态枚举收窄。按规矩破坏性变更该走三步弃用 —— 这里一步改完,因为: ① depcruise 反查下游全在仓内(374 模块无违规),同批改完; ② 要弃用的是一个**产品决定不再存在**的机制,留弃用窗口没有意义。 CONTRACT.md 已同步,快照单独说明。 typecheck ✓ lint ✓ depcruise 374 模块 ✓ 495 测试 ✓ Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -16,7 +16,6 @@ import {
|
||||
useGovActivities,
|
||||
useGovObjects,
|
||||
useGovPackages,
|
||||
useGovPkgReports,
|
||||
useGovTools,
|
||||
useGovTriggers,
|
||||
} from '@web-shared/gov/gov-api.ts';
|
||||
@@ -31,11 +30,12 @@ export function BlueprintHome({ nav }: HomeProps) {
|
||||
const trgs = useGovTriggers();
|
||||
|
||||
const packages = pkgs.data?.packages ?? [];
|
||||
const reports = useGovPkgReports(packages);
|
||||
const reportOf = (id: string, version: number) => reports.data?.get(`${id}@${version}`);
|
||||
|
||||
const drafts = packages.filter((p) => p.state !== 'published');
|
||||
const draftBad = drafts.filter((p) => reportOf(p.id, p.version)?.publishable === false);
|
||||
/*
|
||||
* 没有「草稿」也没有「未闭合」可数了 —— 严格检查 + 保存即生效:不闭合的包**存不进来**。
|
||||
* 硬摆这两个计数就是摆两个恒为 0 的假指标。改数真能说话的:每条流有几版、最新是第几版。
|
||||
*/
|
||||
const flowCount = new Set(packages.map((p) => p.id)).size;
|
||||
const activities = acts.data?.activities ?? [];
|
||||
const toolRows = tools.data?.tools ?? [];
|
||||
const objectRows = objs.data?.objects ?? [];
|
||||
@@ -57,7 +57,7 @@ export function BlueprintHome({ nav }: HomeProps) {
|
||||
(id) => !triggers.some((t) => t.processPackageId === id),
|
||||
);
|
||||
|
||||
const gaps = draftBad.length + missingActivities.length + uncalled.length;
|
||||
const gaps = missingActivities.length + uncalled.length;
|
||||
|
||||
// 因果链是这一屏的主角:四库以真箭头相连 —— 箭头编码的是真实依赖
|
||||
// (流程包引用活动、活动调工具、活动物化数据),节点卡即入口,点卡即入库。
|
||||
@@ -71,9 +71,9 @@ export function BlueprintHome({ nav }: HomeProps) {
|
||||
</span>
|
||||
战略 · 流程包
|
||||
</span>
|
||||
<span className="g-chain-v">{packages.length}</span>
|
||||
<span className="g-chain-v">{flowCount}</span>
|
||||
<span className="g-chain-h">
|
||||
企业以哪些流转运转 —— 草稿 {drafts.length} · 未闭合 {draftBad.length}
|
||||
企业以哪些流转运转 —— 共 {packages.length} 个版本,保存即生效
|
||||
</span>
|
||||
</button>
|
||||
<span className="g-chain-arr">
|
||||
@@ -124,32 +124,9 @@ export function BlueprintHome({ nav }: HomeProps) {
|
||||
|
||||
<Sec ico="flag" title="待补清单" extra="定义上的缺口聚在这里,点进即改 —— 不散落在四库">
|
||||
{gaps === 0 ? (
|
||||
<Clear>没有缺口 —— 流程包全部闭合,引用的活动都装好了,每条流也都有触发器调得起来。</Clear>
|
||||
<Clear>没有缺口 —— 引用的活动都装好了,每条流也都有触发器调得起来。</Clear>
|
||||
) : (
|
||||
<div className="g-todo">
|
||||
{draftBad.map((p) => (
|
||||
<Todo
|
||||
key={`bad-${p.id}@${p.version}`}
|
||||
tone="warn"
|
||||
ico="git-merge"
|
||||
title={`${p.name} v${p.version} · 闭合校验未过`}
|
||||
desc={
|
||||
(reportOf(p.id, p.version)?.violations ?? []).map((v) => v.message).join(';') ||
|
||||
'存在未通过闭合校验的声明'
|
||||
}
|
||||
acts={[
|
||||
{ label: '去编排', ico: 'route', onClick: () => nav.push({ k: 'pkg', id: `${p.id}@${p.version}` }) },
|
||||
{
|
||||
label: '看边映射',
|
||||
ico: 'git-merge',
|
||||
onClick: () => {
|
||||
nav.push({ k: 'pkg', id: `${p.id}@${p.version}` });
|
||||
nav.push({ k: 'edges', id: `${p.id}@${p.version}` });
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
{missingActivities.map((id) => (
|
||||
<Todo
|
||||
key={`miss-${id}`}
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
useGovActivities,
|
||||
useGovMemory,
|
||||
useGovMetrics,
|
||||
useGovPackages,
|
||||
useGovPkgReports,
|
||||
useGovTasks,
|
||||
} from '@web-shared/gov/gov-api.ts';
|
||||
|
||||
@@ -44,17 +42,12 @@ const TERMINAL_TONE: Readonly<Record<string, string>> = {
|
||||
export function CockpitHome({ nav }: HomeProps) {
|
||||
const metrics = useGovMetrics();
|
||||
const tasks = useGovTasks();
|
||||
const packages = useGovPackages();
|
||||
const reports = useGovPkgReports(packages.data?.packages ?? []);
|
||||
const m = metrics.data;
|
||||
|
||||
const workflows = tasks.data?.workflows ?? [];
|
||||
const taskRows = tasks.data?.tasks ?? [];
|
||||
const abnormal = workflows.filter((w) => w.state === 'abnormal').length;
|
||||
const running = taskRows.filter((t) => t.terminal === undefined).length;
|
||||
const draftBad = (packages.data?.packages ?? []).filter(
|
||||
(p) => p.state !== 'published' && reports.data?.get(`${p.id}@${p.version}`)?.publishable === false,
|
||||
).length;
|
||||
|
||||
const mix = m?.terminalMix ?? {};
|
||||
const mixTop = Object.entries(mix)
|
||||
@@ -72,7 +65,12 @@ export function CockpitHome({ nav }: HomeProps) {
|
||||
value={abnormal}
|
||||
sub="失败走向未消化,待人工处置"
|
||||
/>
|
||||
<Kpi ico="git-merge" tone="warn" label="未闭合草稿" value={draftBad} sub="边不闭合不得发布" />
|
||||
{/*
|
||||
* 「未闭合草稿」这张卡撤了 —— 严格检查 + 保存即生效之后,不闭合的包**存不进来**,
|
||||
* 这个数结构上恒为 0。摆一个永远是 0 的卡位就是在占地方装样子。
|
||||
* 天然的第三张是「死路」(网关一条边都判不中而卡住的流,已有 route-dead-end 事件),
|
||||
* 但那需要一条按事件类型聚合的查询路由 —— 没有之前不摆(同死信卡的规矩)。
|
||||
*/}
|
||||
<Kpi ico="activity" tone="brand" label="在途任务" value={running} sub="尚未落终态的任务" />
|
||||
</div>
|
||||
{abnormal > 0 ? (
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* `WorkflowsHome` / `PkgPage` / `EdgesPage` 平移:DOM 结构、类名(`g-*`)与文案原样保留,
|
||||
* mock 常量换成服务端投影(README 平移规则 1/2/4)。
|
||||
*
|
||||
* 编排即定契约:连线即映射、画图即校验、**边不闭合不得发布**;
|
||||
* 编排即定契约:连线即映射、画图即校验、**边不闭合不得保存**;
|
||||
* 图画完可导出活动开发的依赖文件 —— 开发者对着声明在系统外开发。
|
||||
*/
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
|
||||
import type { ClosureReport, ProcessPackage, ProcessNode } from '@contracts';
|
||||
import { publishProcessRoute, saveProcessRoute, validateProcessRoute, TRIGGER_KIND_LABEL } from '@contracts';
|
||||
import { saveProcessRoute, validateProcessRoute, TRIGGER_KIND_LABEL } from '@contracts';
|
||||
import { call } from '@web-shared/typed-fetch.ts';
|
||||
import { Ico } from '@web-shared/Ico.tsx';
|
||||
import {
|
||||
@@ -79,15 +79,21 @@ function PackagesHome({ tab, nav }: HomeProps) {
|
||||
return (
|
||||
<QueryGate query={q}>
|
||||
<div className="g-shelf">
|
||||
{/*
|
||||
* 没有「已发布 / 草稿」了 —— 严格检查 + 保存即生效。
|
||||
* 真正的区别是「哪一版会被新起单取到」:同一条流里**版本号最大的那一版生效中**,
|
||||
* 更早的版本是历史(在途任务还照它跑完)。
|
||||
*/}
|
||||
{packages.map((p) => {
|
||||
const published = p.state === 'published';
|
||||
const latestOfFlow = Math.max(...packages.filter((x) => x.id === p.id).map((x) => x.version));
|
||||
const live = p.version === latestOfFlow && p.state !== 'retired';
|
||||
const ec = edgeCheckOf(p, reportOf(p));
|
||||
const okEdge = ec.closed === ec.total;
|
||||
const c = counts(p);
|
||||
return (
|
||||
<button
|
||||
key={`${p.id}@${p.version}`}
|
||||
className={'g-tile t-' + (published ? 'ok' : 'warn')}
|
||||
className={'g-tile t-' + (live ? 'ok' : 'mute')}
|
||||
onClick={() => nav.push({ k: 'pkg', id: `${p.id}@${p.version}` })}
|
||||
>
|
||||
<span className="g-tile-h">
|
||||
@@ -98,13 +104,13 @@ function PackagesHome({ tab, nav }: HomeProps) {
|
||||
<b>{p.name}</b>
|
||||
<em>v{p.version}</em>
|
||||
</span>
|
||||
{published ? (
|
||||
{live ? (
|
||||
<St tone="ok" ico="circle-check">
|
||||
已发布
|
||||
生效中
|
||||
</St>
|
||||
) : (
|
||||
<St tone="warn" ico="square-pen">
|
||||
草稿
|
||||
<St tone="mute" ico="history">
|
||||
历史版本
|
||||
</St>
|
||||
)}
|
||||
</span>
|
||||
@@ -133,24 +139,24 @@ function PackagesHome({ tab, nav }: HomeProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const published = packages.filter((p) => p.state === 'published').length;
|
||||
const flows = new Set(packages.map((p) => p.id)).size;
|
||||
const totalEdges = packages.reduce((a, p) => a + p.edges.length, 0);
|
||||
const closedEdges = packages.reduce((a, p) => a + edgeCheckOf(p, reportOf(p)).closed, 0);
|
||||
return (
|
||||
<QueryGate query={q}>
|
||||
<div className="g-kpis">
|
||||
<Kpi ico="circle-check" tone="ok" label="已发布流程包" value={published} sub="版本并存 · 在途按启动版本跑完" />
|
||||
<Kpi ico="square-pen" tone="warn" label="草稿" value={packages.length - published} sub="边不闭合不得发布" />
|
||||
<Kpi ico="workflow" tone="ok" label="流程" value={flows} sub="每条流的最新一版生效中" />
|
||||
<Kpi ico="history" label="版本" value={packages.length} sub="恒新增 · 在途按启动版本跑完" />
|
||||
<Kpi
|
||||
ico="git-merge"
|
||||
label="边闭合率"
|
||||
value={totalEdges === 0 ? 100 : Math.round((closedEdges / totalEdges) * 100)}
|
||||
unit="%"
|
||||
sub="连线即映射,画图即校验"
|
||||
sub="不闭合存不进来"
|
||||
/>
|
||||
<Kpi ico="boxes" label="编排要素" value="五种" sub="活动 · 审批 · 网关 · 触发器 · 流程包" />
|
||||
</div>
|
||||
<Note>恒新增、版本并存:发布不覆盖旧版本,此后新起的工作流用新版本,在途的按启动时版本跑完。</Note>
|
||||
<Note>严格检查 + 保存即生效:不闭合的编排存不进来;存下来即刻对**新起的**工作流生效,在途的按启动时版本跑完。保存恒新增版本,旧版本原样留着。</Note>
|
||||
</QueryGate>
|
||||
);
|
||||
}
|
||||
@@ -172,6 +178,7 @@ export function PkgPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
);
|
||||
}
|
||||
const siblings = (q.data?.packages ?? []).filter((x) => x.id === pkg.id);
|
||||
const latestVersion = Math.max(...siblings.map((x) => x.version));
|
||||
return (
|
||||
<Orchestrator
|
||||
key={`${pkg.id}@${pkg.version}`}
|
||||
@@ -179,8 +186,9 @@ export function PkgPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
nav={nav}
|
||||
activities={acts.data?.activities ?? []}
|
||||
positions={(staffQ.data?.positions ?? []).map((p) => ({ id: p.id, name: p.name, abbr: p.code }))}
|
||||
nextVersion={Math.max(...siblings.map((x) => x.version)) + 1}
|
||||
draftSibling={siblings.filter((x) => x.state !== 'published').sort((a, b) => b.version - a.version)[0]}
|
||||
nextVersion={latestVersion + 1}
|
||||
/* 生效中 = 这条流版本号最大的那一版:只有它能改(改则存成下一版);历史版本只读 */
|
||||
live={pkg.version === latestVersion && pkg.state !== 'retired'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -201,17 +209,18 @@ function Orchestrator({
|
||||
activities,
|
||||
positions,
|
||||
nextVersion,
|
||||
draftSibling,
|
||||
live,
|
||||
}: {
|
||||
pkg: ProcessPackage;
|
||||
nav: Nav;
|
||||
activities: readonly ActivityRow[];
|
||||
positions: SpPosition[];
|
||||
nextVersion: number;
|
||||
draftSibling: ProcessPackage | undefined;
|
||||
live: boolean;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const published = pkg.state === 'published';
|
||||
/** 只读的是**历史版本**(不是「已发布」)—— 生效中的那一版可以改,改则存成下一版 */
|
||||
const readOnly = !live;
|
||||
const paradigms = useMemo(() => new Map(activities.map((a) => [a.activityId, a.paradigm])), [activities]);
|
||||
|
||||
/** 画布工作副本:属性卡与手绘编辑都改它;保存时合并回流程包 */
|
||||
@@ -239,7 +248,7 @@ function Orchestrator({
|
||||
});
|
||||
const activeReport = report ?? liveReport.data;
|
||||
const ec = edgeCheckOf(pkg, activeReport);
|
||||
const okEdge = activeReport?.publishable === true;
|
||||
const okEdge = activeReport?.closed === true;
|
||||
|
||||
const patch = (f: (n: FlowNode) => FlowNode) => {
|
||||
if (sel === null) return;
|
||||
@@ -296,29 +305,6 @@ function Orchestrator({
|
||||
},
|
||||
});
|
||||
|
||||
const publish = useMutation({
|
||||
mutationFn: () =>
|
||||
call<ClosureReport>(publishProcessRoute, { params: { id: pkg.id, version: String(pkg.version) } }),
|
||||
onSuccess: (r) => {
|
||||
setReport(r);
|
||||
void queryClient.invalidateQueries({ queryKey: ['gov', 'process'] });
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 起草新版本 —— 已发布不可回改,改图的路是**克隆成下一版草稿**再编辑:
|
||||
* 连手绘存证一起带走,落地即跳到新草稿页(那里「编辑」芯片与属性卡全开)。
|
||||
*/
|
||||
const draftNext = useMutation({
|
||||
mutationFn: () =>
|
||||
call<{ pkg: ProcessPackage; report: ClosureReport }>(saveProcessRoute, {
|
||||
body: { pkg: { ...pkg, version: nextVersion, state: 'draft' } },
|
||||
}),
|
||||
onSuccess: (out) => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['gov', 'process'] });
|
||||
nav.push({ k: 'pkg', id: `${out.pkg.id}@${out.pkg.version}` });
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 导出依赖文件 —— 编排定契约:各活动的接口声明、责任岗位、输入/输出声明与上游装配,
|
||||
@@ -373,18 +359,18 @@ function Orchestrator({
|
||||
<>
|
||||
<PageHead
|
||||
ico="workflow"
|
||||
tone={published ? 'ok' : 'warn'}
|
||||
tone={live ? 'ok' : 'mute'}
|
||||
kicker={`${pkg.id} · v${pkg.version}`}
|
||||
title={`${pkg.name} v${pkg.version}`}
|
||||
chips={
|
||||
<>
|
||||
{published ? (
|
||||
{live ? (
|
||||
<St tone="ok" ico="circle-check">
|
||||
已发布
|
||||
生效中
|
||||
</St>
|
||||
) : (
|
||||
<St tone="warn" ico="square-pen">
|
||||
草稿
|
||||
<St tone="mute" ico="history">
|
||||
历史版本
|
||||
</St>
|
||||
)}
|
||||
<St
|
||||
@@ -405,38 +391,21 @@ function Orchestrator({
|
||||
</>
|
||||
}
|
||||
acts={[
|
||||
...(published
|
||||
? [
|
||||
draftSibling !== undefined
|
||||
? {
|
||||
label: `去草稿 v${draftSibling.version} 编辑`,
|
||||
ico: 'square-pen',
|
||||
primary: true,
|
||||
onClick: () => nav.push({ k: 'pkg', id: `${draftSibling.id}@${draftSibling.version}` }),
|
||||
}
|
||||
: {
|
||||
label: '起草新版本',
|
||||
ico: 'square-pen',
|
||||
primary: true,
|
||||
ask: {
|
||||
title: `起草新版本:${pkg.name} v${nextVersion}`,
|
||||
note: (
|
||||
<Note>
|
||||
已发布版本不可回改 —— 改图的路是克隆成 v{nextVersion} 草稿再编排,
|
||||
手绘几何与全部声明一并带走。
|
||||
</Note>
|
||||
),
|
||||
impact: {
|
||||
who: '仅新草稿;本版与在途工作流不受影响',
|
||||
undo: '草稿不发布就不生效,可反复改',
|
||||
when: '确认后立即建稿并跳转',
|
||||
},
|
||||
okLabel: '建稿并去编辑',
|
||||
onOk: () => draftNext.mutate(),
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
/*
|
||||
* 历史版本只读 —— 改图要回到生效中的那一版(在那儿保存会存成下一版)。
|
||||
* 从前这里是「已发布不可回改 → 起草新版本 / 去草稿编辑」两条路;
|
||||
* 没有草稿之后只剩一条:生效版就地改,保存即新版。
|
||||
*/
|
||||
...(live
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: `去 v${nextVersion - 1} 改(生效中)`,
|
||||
ico: 'square-pen',
|
||||
primary: true,
|
||||
onClick: () => nav.push({ k: 'pkg', id: `${pkg.id}@${nextVersion - 1}` }),
|
||||
},
|
||||
]),
|
||||
{
|
||||
label: '导出依赖文件',
|
||||
ico: 'upload',
|
||||
@@ -453,71 +422,41 @@ function Orchestrator({
|
||||
onOk: exportDeps,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '发布',
|
||||
ico: 'send',
|
||||
primary: true,
|
||||
disabled: published,
|
||||
ask: {
|
||||
title: `发布:${pkg.name} v${pkg.version}`,
|
||||
note: okEdge ? (
|
||||
<Note tone="ok">边映射全闭合、必填齐、类型无冲突 —— 可发布。</Note>
|
||||
) : (
|
||||
<Note tone="warn">{activeReport?.violations.map((v) => v.message).join(';') ?? '闭合校验未跑完'}</Note>
|
||||
),
|
||||
impact: {
|
||||
who: okEdge ? '此后新起的工作流用新版本;在途按启动时版本跑完' : '被阻断 —— 先补齐边映射',
|
||||
undo: okEdge ? '版本并存,可停用回退' : '—',
|
||||
when: okEdge ? '确认后即时' : '不生效',
|
||||
},
|
||||
okLabel: okEdge ? '确认发布' : '我知道了',
|
||||
onOk: () => {
|
||||
if (okEdge) publish.mutate();
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{activeReport !== undefined && !activeReport.publishable && (
|
||||
{activeReport !== undefined && !activeReport.closed && (
|
||||
<Note tone="warn">
|
||||
<b>未闭合:</b>
|
||||
{activeReport.violations.map((v) => v.message).join(';')}
|
||||
</Note>
|
||||
)}
|
||||
{report !== undefined && report.publishable && <Note tone="ok">闭合校验通过。</Note>}
|
||||
{report !== undefined && report.closed && <Note tone="ok">闭合校验通过。</Note>}
|
||||
|
||||
{/* 编排工作区:左 2/3 画布(整图显示,不裁切)+ 右 1/3 属性卡(就地填写,不下钻) */}
|
||||
<Sec
|
||||
ico="route"
|
||||
title="编排工作区"
|
||||
extra={published ? '已发布版本只读 —— 改图要另起一版' : '点画布上的活动 → 右栏属性卡直接改;「编辑」进手绘模式'}
|
||||
extra={readOnly ? '历史版本只读 —— 改图回到生效中那一版' : '点画布上的活动 → 右栏属性卡直接改;「编辑」进手绘模式;保存即生效'}
|
||||
>
|
||||
{/*
|
||||
已发布不可回改是规矩,但「去哪儿改」不该让人自己找 ——
|
||||
就地把路指出来:一句为什么 + 一颗按钮,点了直接建稿并跳过去。
|
||||
历史版本只读,但「去哪儿改」不该让人自己找 —— 就地把路指出来。
|
||||
从前这里是「克隆成草稿再编排」;没有草稿之后,生效版就地改、保存即存成下一版。
|
||||
*/}
|
||||
{published && (
|
||||
{readOnly && (
|
||||
<div className="g-todo">
|
||||
<Todo
|
||||
tone="warn"
|
||||
ico="square-pen"
|
||||
title={`v${pkg.version} 已发布,画布只读`}
|
||||
desc={`改图的路是克隆成 v${nextVersion} 草稿再编排 —— 手绘几何与全部声明一并带走,本版与在途工作流不受影响。${
|
||||
draftSibling === undefined ? '' : `(已有 v${draftSibling.version} 草稿在,直接去那儿改。)`
|
||||
}`}
|
||||
ico="history"
|
||||
title={`v${pkg.version} 是历史版本,画布只读`}
|
||||
desc={`在途任务还按这一版跑完,所以它不可改。改图去 v${nextVersion - 1}(生效中那一版)——`
|
||||
+ `在那儿保存会存成 v${nextVersion},本版与在途工作流一概不动。`}
|
||||
acts={[
|
||||
draftSibling === undefined
|
||||
? {
|
||||
label: draftNext.isPending ? '建稿中…' : `起草 v${nextVersion} 并去编辑`,
|
||||
ico: 'square-pen',
|
||||
onClick: () => draftNext.mutate(),
|
||||
}
|
||||
: {
|
||||
label: `去草稿 v${draftSibling.version} 编辑`,
|
||||
ico: 'square-pen',
|
||||
onClick: () => nav.push({ k: 'pkg', id: `${draftSibling.id}@${draftSibling.version}` }),
|
||||
},
|
||||
{
|
||||
label: `去 v${nextVersion - 1} 改`,
|
||||
ico: 'square-pen',
|
||||
onClick: () => nav.push({ k: 'pkg', id: `${pkg.id}@${nextVersion - 1}` }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -533,7 +472,7 @@ function Orchestrator({
|
||||
graph={graph}
|
||||
drilledIds={nodes.filter((n) => n.kind === 'activity').map((n) => n.id)}
|
||||
positions={positions}
|
||||
canEdit={!published}
|
||||
canEdit={!readOnly}
|
||||
onNodeClick={(nid) => setSel(nid)}
|
||||
onEditNodeText={async (nid, p2) => {
|
||||
setGraph((g) =>
|
||||
@@ -769,24 +708,31 @@ function Orchestrator({
|
||||
align="end"
|
||||
items={[
|
||||
{
|
||||
label: dirty ? '保存编排' : '编排无改动',
|
||||
label: dirty ? `保存并生效(存为 v${nextVersion})` : '编排无改动',
|
||||
ico: 'send',
|
||||
primary: dirty,
|
||||
disabled: !dirty || published,
|
||||
disabled: !dirty || readOnly,
|
||||
ask: dirty
|
||||
? {
|
||||
title: `保存编排:${pkg.name} v${pkg.version}`,
|
||||
note: (
|
||||
<Note>
|
||||
编排定契约:改活动声明即改边映射两端,保存后按新拓扑重算闭合。定义对在途任务不可变。
|
||||
title: `保存并生效:${pkg.name} → v${nextVersion}`,
|
||||
note: okEdge ? (
|
||||
<Note tone="ok">
|
||||
边映射全闭合、必填齐、类型无冲突 —— 存下去即刻对新起的工作流生效。
|
||||
</Note>
|
||||
) : (
|
||||
<Note tone="warn">
|
||||
<b>不闭合存不进来:</b>
|
||||
{activeReport?.violations.map((v) => v.message).join(';') ?? '闭合校验未跑完'}
|
||||
</Note>
|
||||
),
|
||||
impact: {
|
||||
who: published ? '已发布版本不可回改 —— 另起新版本' : '本草稿版本的编排定义',
|
||||
undo: '版本并存,可回退上一版本',
|
||||
when: '保存即入草稿;发布后新起的工作流才用它',
|
||||
who: okEdge
|
||||
? `存为 v${nextVersion},此后**新起**的工作流用它;在途的按启动时版本跑完`
|
||||
: '被拒存 —— 先补齐边映射',
|
||||
undo: `v${pkg.version} 原样留着,可回到那一版再改`,
|
||||
when: okEdge ? '确认即生效,没有发布这一步' : '存不进去',
|
||||
},
|
||||
okLabel: '确认保存',
|
||||
okLabel: okEdge ? '确认保存并生效' : '我知道了',
|
||||
onOk: () => {
|
||||
if (graph !== null) save.mutate({ graph, idMap: {} });
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ const basePkg: ProcessPackage = processPackageSchema.parse({
|
||||
id: 'p',
|
||||
name: '包',
|
||||
version: 1,
|
||||
state: 'draft',
|
||||
state: 'active',
|
||||
callInterface: { input: { type: 'object' }, output: { type: 'object' } },
|
||||
nodes: [
|
||||
{ id: 'S', kind: 'start', label: '起' },
|
||||
|
||||
Reference in New Issue
Block a user