forked from ChaiHao/BPOS-MVP
删掉流程详情的「契约细节」段 —— 页面到节点属性为止
它的三块都已有更好的去处或本就冗余:边映射的问题由异常看板逐条点名(还能点击定位), 调用接口在画布「开始」节点的属性里,触发器有自己的页签。 连带清掉因此变成死代码的整页 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:
@@ -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':
|
||||||
|
|||||||
@@ -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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
Reference in New Issue
Block a user