删掉流程详情的「契约细节」段 —— 页面到节点属性为止

它的三块都已有更好的去处或本就冗余:边映射的问题由异常看板逐条点名(还能点击定位),
调用接口在画布「开始」节点的属性里,触发器有自己的页签。
连带清掉因此变成死代码的整页 EdgesPage(路由类型 / 壳分发 / 出口 / 死 import 一并删,
不留只进不出的入口)。

详情页现在就四件:版本卡 · 流程图 · 异常看板 · 节点属性。

typecheck ✓ lint ✓ depcruise ✓ 506 测试 ✓

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-28 16:54:51 +08:00
co-authored by Claude Opus 5
parent a57da2f5ca
commit 448285776c
4 changed files with 4 additions and 189 deletions
+1 -3
View File
@@ -26,7 +26,7 @@ import { queryKeys } from './query.ts';
import { BlueprintHome } from '@features/gov-blueprint'; import { BlueprintHome } from '@features/gov-blueprint';
import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task'; import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task';
import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff'; import { EmpPage, PeopleHome, RolePage } from '@features/gov-staff';
import { EdgesPage, PkgPage, WorkflowsHome } from '@features/gov-process'; import { PkgPage, WorkflowsHome } from '@features/gov-process';
import { TriggerPage } from '@features/gov-trigger'; import { TriggerPage } from '@features/gov-trigger';
import { ActPage, LibraryHome } from '@features/gov-activity'; import { ActPage, LibraryHome } from '@features/gov-activity';
import { ToolPage, ToolsHome } from '@features/gov-tool'; import { ToolPage, ToolsHome } from '@features/gov-tool';
@@ -308,8 +308,6 @@ function Detail({ route, nav }: { route: Route; nav: Nav }) {
return <RolePage id={route.id} />; return <RolePage id={route.id} />;
case 'pkg': case 'pkg':
return <PkgPage id={route.id} nav={nav} />; return <PkgPage id={route.id} nav={nav} />;
case 'edges':
return <EdgesPage id={route.id} />;
case 'trigger': case 'trigger':
return <TriggerPage id={route.id} />; return <TriggerPage id={route.id} />;
case 'act': case 'act':
+1 -1
View File
@@ -2,4 +2,4 @@
* feature `gov-process` 唯一出口(编码规范 §9.1)。 * feature `gov-process` 唯一出口(编码规范 §9.1)。
* 流程包编排画布(diagram-js 画布件平移):连线即映射、画图即校验、不闭合不得发布 * 流程包编排画布(diagram-js 画布件平移):连线即映射、画图即校验、不闭合不得发布
*/ */
export { WorkflowsHome, PkgPage, EdgesPage } from './internal/panel.tsx'; export { WorkflowsHome, PkgPage } from './internal/panel.tsx';
@@ -13,7 +13,7 @@
import { useMemo, useRef, useState, useEffect } from 'react'; import { useMemo, useRef, useState, useEffect } from 'react';
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
import type { ClosureReport, ProcessPackage, ProcessNode } from '@contracts'; import type { ClosureReport, ProcessPackage, ProcessNode } from '@contracts';
import { processPackageSchema, saveProcessRoute, setProcessStateRoute, validateProcessRoute, TRIGGER_KIND_LABEL } from '@contracts'; import { processPackageSchema, saveProcessRoute, setProcessStateRoute, validateProcessRoute } from '@contracts';
import { call } from '@web-shared/typed-fetch.ts'; import { call } from '@web-shared/typed-fetch.ts';
import { Ico } from '@web-shared/Ico.tsx'; import { Ico } from '@web-shared/Ico.tsx';
import type { AskSpec } from '@web-shared/gov/primitives.tsx'; import type { AskSpec } from '@web-shared/gov/primitives.tsx';
@@ -22,23 +22,19 @@ import {
AskBand, AskBand,
Card, Card,
Chip, Chip,
Entry,
Field, Field,
Kpi, Kpi,
ListEdit, ListEdit,
NList,
NRow,
Note, Note,
PageHead, PageHead,
Prop, Prop,
QueryGate, QueryGate,
Rows,
Sec, Sec,
Seg, Seg,
St, St,
} from '@web-shared/gov/primitives.tsx'; } from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts'; import type { Nav } from '@web-shared/gov/gov-route.ts';
import { useGovActivities, useGovPackages, useGovPkgReports, useGovStaff, useGovTriggers } from '@web-shared/gov/gov-api.ts'; import { useGovActivities, useGovPackages, useGovPkgReports, useGovStaff } from '@web-shared/gov/gov-api.ts';
import type { GovActivities } 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 { FlowFigureBlock } from '@web-shared/gov/flow/FlowFigure.tsx';
import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts'; import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
@@ -567,9 +563,6 @@ function FlowLab({
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}; };
const triggersQ = useGovTriggers();
const boundTriggers = (triggersQ.data?.triggers ?? []).filter((t) => t.processPackageId === pkg.id);
const callInput = pkg.callInterface.input as { required?: string[]; properties?: Record<string, { type?: string }> };
return ( return (
<> <>
@@ -990,179 +983,6 @@ function FlowLab({
</div> </div>
</Sec> </Sec>
{/* 声明式契约细节:调用接口 · 边映射 · 触发器 —— 图之下,按查看频率排 */}
<Sec ico="file-text" title="契约细节" extra="声明式 —— 图定结构,这里定字段与调起">
<div className="g-entries">
<Entry
ico="git-merge"
tone={ec.closed === ec.total ? 'ok' : 'danger'}
title="边映射校验"
desc="上游输出口 → 下游契约字段 · 类型与必填,逐边核对"
meta={
<Chip tone={ec.closed === ec.total ? 'ok' : 'danger'}>
{ec.closed}/{ec.total}
</Chip>
}
onClick={() => nav.push({ k: 'edges', id: `${pkg.id}@${pkg.version}` })}
/>
</div>
<Card ico="plug" title="调用接口" extra="起单入参 —— 触发器与登记表单都按它装配">
{Object.keys(callInput.properties ?? {}).length === 0 ? (
<Note>这条流还没声明入参 —— 谁调起它都不用带数据;要带,去画布点「开始」节点声明。</Note>
) : (
<Rows
items={Object.entries(callInput.properties ?? {}).map(([k, v]) => ({
k,
v: `${v.type ?? 'any'}${(callInput.required ?? []).includes(k) ? ' · 必填' : ''}`,
mono: true,
}))}
/>
)}
</Card>
<Card ico="zap" title="调起它的触发器" extra="平级、不入包版本 —— 增删启停不改本流任何版本">
{boundTriggers.length > 0 ? (
<NList>
{boundTriggers.map((t) => (
<NRow
key={t.id}
tone="warn"
ico="zap"
title={t.name}
sub={
t.spec.kind === 'event'
? t.spec.rule
: t.spec.kind === 'registration'
? TRIGGER_KIND_LABEL.registration
: TRIGGER_KIND_LABEL.periodic
}
cols={[{ k: '状态', v: t.enabled ? '已启用' : '已停用' }]}
right={
<button
className="g-btn"
onClick={(e) => {
e.stopPropagation();
nav.jump('workflows', 'rules');
}}
>
改规则
</button>
}
onClick={() => nav.jump('workflows', 'rules', { k: 'trigger', id: t.id })}
/>
))}
</NList>
) : (
<Note>
还没有触发器指向这条流 —— 它定义得再完整,也没人调得起来。去<b>触发器</b>页签配一条
(周期 / 事件 / 登记三选一)。
</Note>
)}
</Card>
</Sec>
</>
);
}
/* ---- 边映射校验(整页) ---- */
export function EdgesPage({ id }: { id: string }) {
const [pid, versionText] = id.split('@');
const q = useGovPackages();
const pkg = (q.data?.packages ?? []).find((p) => p.id === pid && String(p.version) === versionText);
const reports = useGovPkgReports(pkg === undefined ? [] : [pkg]);
if (pkg === undefined) {
return (
<QueryGate query={q}>
<Note>流程包不存在。</Note>
</QueryGate>
);
}
const report = reports.data?.get(`${pkg.id}@${pkg.version}`);
const ec = edgeCheckOf(pkg, report);
const okEdge = ec.closed === ec.total;
const labelOf = (nodeId: string) => pkg.nodes.find((n) => n.id === nodeId)?.label ?? nodeId;
const sourceText = (m: ProcessPackage['edges'][number]['mapping'][number]) => {
const s = m.source;
switch (s.from) {
case 'initiation':
return `起单.${s.path}`;
case 'output-port':
return `${s.port}.${s.path}`;
case 'workflow-var':
return `流程变量.${s.name}`;
case 'constant':
return `常量 ${JSON.stringify(s.value)}`;
default:
return '表达式';
}
};
return (
<>
<PageHead
ico="git-merge"
tone={okEdge ? 'ok' : 'danger'}
kicker={`${pkg.name} v${pkg.version} · 边映射`}
title="边映射校验"
chips={
<St tone={okEdge ? 'ok' : 'danger'} ico={okEdge ? 'circle-check' : 'alert-circle'}>
{ec.closed} / {ec.total} 已闭合
</St>
}
desc="连线即映射:每条顺序边都要把上游输出口接到下游契约字段上,必填齐、类型无冲突才算闭合。边不闭合不得保存。"
/>
{!okEdge && report !== undefined && <Note tone="warn">{report.violations.map((v) => v.message).join(';')}</Note>}
<Card ico="git-merge" title="逐边核对" extra="上游输出口 → 下游契约字段">
<table className="g-matrix">
<thead>
<tr>
<th>边</th>
<th>映射</th>
<th>条数</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{pkg.edges.map((e) => {
const bad = (report?.violations ?? []).filter((v) => v.edgeId === e.id);
return (
<tr key={e.id}>
<td className="mono">
{labelOf(e.from)} → {labelOf(e.to)}
{e.label === undefined ? '' : `(${e.label})`}
</td>
<td>
{e.mapping.length === 0
? '—'
: e.mapping.map((m) => `${sourceText(m)} → data.${m.target}`).join('; ')}
{bad.length > 0 && (
<>
<br />
<span className="g-mono">{bad.map((v) => v.message).join(';')}</span>
</>
)}
</td>
<td className="mono">{e.mapping.length}</td>
<td>
{bad.length === 0 ? (
<St tone="ok" ico="circle-check">
已闭合
</St>
) : (
<St tone="danger" ico="alert-circle">
未完成
</St>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</Card>
</> </>
); );
} }
-3
View File
@@ -13,7 +13,6 @@ export type Route =
| { k: 'emp'; id: string } | { k: 'emp'; id: string }
| { k: 'role'; id: string } | { k: 'role'; id: string }
| { k: 'pkg'; id: string } | { k: 'pkg'; id: string }
| { k: 'edges'; id: string }
| { k: 'trigger'; id: string } | { k: 'trigger'; id: string }
| { k: 'act'; id: string } | { k: 'act'; id: string }
| { k: 'tool'; id: string } | { k: 'tool'; id: string }
@@ -190,8 +189,6 @@ export function routeLabel(r: Route): string {
return '岗位授权'; return '岗位授权';
case 'pkg': case 'pkg':
return '流程包'; return '流程包';
case 'edges':
return '边映射';
case 'trigger': case 'trigger':
return '触发器'; return '触发器';
case 'act': case 'act':