三面拆分⑭:纯编排第三轮 —— 活动卸岗位、卡点废除全真边、边号 E1–En 图上可选
活动契约不带负责人岗位(用户定): - schema:活动节点删 owner(审批点的 approver 是「卡到人的工位」的功能语义,保留) - 运行时:活动任务默认**全员轮值**(在岗候选并集、最闲者先、staffId 稳定排序, owner-resolved 事件岗位记「(全员轮值)」);运营分派机制落地后让位 —— 债已记 - closure:活动点不核岗位,missing-owner-position 只出自审批点 - 员工面链条:活动点报**实际承接人**(实例→任务→人),没起的如实报「轮值待派」; NodeInstance 公开 taskId/approvalId(店面读回,与端口型合流) - 画布/导出依赖文件/属性卡全线摘岗位;属性卡活动点明说「分派归运营」 卡点画法废除,顺序流一律真边(用户定): - 种子图重画:四条回跳/直达(E4 直接定价、E6 选型后定价、E10 不通过、E12 有异议) 全部画成折线真边,车道分层零交叉(顶 y30 / y190 / y230 / 缝隙对开 / 底 y380); 卡点节点删净,图=恰 10 语义节点 + 14 真边一一对应(测试守着) - graph-to-pkg 卡点认亲机制删除;pkg-to-graph 直接按 from→to 认亲 - 元素盘删卡点件;图例、属性卡卡点分支、kindLabel/kindTone 随删 边号 E1、E2…(用户定): - 语义边 id 即边号:种子 E1–E14;合并器铸号(旧边保号、新边取下一空号,回归测试守着) - 画布线标带号(`E3 直接寻源`,无注记的边也标 `E1`);拣选条以边号立牌, **条件不再上按钮** —— 条件/映射只住属性面 - 边在图上可选中:FlowDiagram 连线加宽透明命中垫(边可点时注解层提到形状层上), 点线即切属性卡到该边(实测 E4 命中) 测试:closure 28、advance-order 6、graph-to-pkg 5(新增铸号例)、seed 8、 端到端八例真库全绿(全员轮值下照跑);五闸退出码验过。 库:purge 后重种,唯一 unified-quote@v1 生效(双面判据齐)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -68,14 +68,6 @@ function flowsOf(packages: readonly ProcessPackage[]) {
|
||||
|
||||
/* ── 边属性:传递契约(条件 + 映射)──────────────────────────────────── */
|
||||
|
||||
/** 卡点 id → 去向节点 id(边属性按语义对端认亲:A5→c_reject 即 A5→A4) */
|
||||
function calloutToOf(graph: FlowGraph | null): Map<string, string> | undefined {
|
||||
if (graph === null) return undefined;
|
||||
return new Map(
|
||||
graph.nodes.filter((n) => n.kind === 'callout' && n.to !== undefined).map((n) => [n.id, n.to!]),
|
||||
);
|
||||
}
|
||||
|
||||
/** 映射行的工作形状 —— 语义边 mapping 的元素;设计者只见三种来源,其余是系统隐性上下文 */
|
||||
type MapRow = { target: string; source: Record<string, unknown> & { from: string } };
|
||||
const SOURCE_KINDS = [
|
||||
@@ -84,21 +76,13 @@ const SOURCE_KINDS = [
|
||||
{ k: 'constant', label: '常量' },
|
||||
] as const;
|
||||
|
||||
function EdgeProps({
|
||||
edge,
|
||||
toSem,
|
||||
onPatch,
|
||||
}: {
|
||||
edge: FlowEdge;
|
||||
toSem: string;
|
||||
onPatch: (f: (e: FlowEdge) => FlowEdge) => void;
|
||||
}) {
|
||||
function EdgeProps({ edge, onPatch }: { edge: FlowEdge; onPatch: (f: (e: FlowEdge) => FlowEdge) => void }) {
|
||||
const rows = (edge.mapping ?? []) as MapRow[];
|
||||
const setRows = (next: MapRow[]) => onPatch((e) => ({ ...e, mapping: next }));
|
||||
const patchRow = (i: number, f: (r: MapRow) => MapRow) => setRows(rows.map((r, j) => (j === i ? f(r) : r)));
|
||||
return (
|
||||
<div className="g-props">
|
||||
<Prop ico="git-branch" title={`条件 · ${edge.from} → ${toSem}`} extra="多出边异或:命中即走;留空 = 默认兜底">
|
||||
<Prop ico="git-branch" title={`${edge.code ?? '边'} · ${edge.from} → ${edge.to}`} extra="多出边异或:命中即走;条件留空 = 默认兜底">
|
||||
<Field k="S-FEEL 条件" hint="如 needSourcing = true;可用 and / or 连接。留空即无条件(默认边,至多一条)">
|
||||
<input
|
||||
className="g-input mono"
|
||||
@@ -272,7 +256,7 @@ function minimalFlow(id: string, name: string): ProcessPackage {
|
||||
{ id: 'start', kind: 'start', label: '开始', layout: { row: 0, col: 0, x: 30, y: 70 } },
|
||||
{ id: 'end', kind: 'end', label: '结束', layout: { row: 0, col: 1, x: 150, y: 70 } },
|
||||
],
|
||||
edges: [{ id: 'e-start-end', from: 'start', to: 'end', mapping: [], reopen: false }],
|
||||
edges: [{ id: 'E1', from: 'start', to: 'end', mapping: [], reopen: false }],
|
||||
variables: [],
|
||||
});
|
||||
}
|
||||
@@ -532,17 +516,8 @@ function FlowLab({
|
||||
};
|
||||
|
||||
const kindLabel = (n: FlowNode) =>
|
||||
n.kind === 'activity'
|
||||
? n.flavor === 'approval'
|
||||
? '审批点'
|
||||
: '活动点'
|
||||
: n.kind === 'callout'
|
||||
? '卡点'
|
||||
: n.kind === 'start'
|
||||
? '调用接口'
|
||||
: '显式终点';
|
||||
const kindTone = (n: FlowNode) =>
|
||||
n.kind === 'activity' ? (n.flavor === 'approval' ? 'warn' : 'auto') : n.kind === 'callout' ? 'danger' : 'mute';
|
||||
n.kind === 'activity' ? (n.flavor === 'approval' ? '审批点' : '活动点') : n.kind === 'start' ? '调用接口' : '显式终点';
|
||||
const kindTone = (n: FlowNode) => (n.kind === 'activity' ? (n.flavor === 'approval' ? 'warn' : 'auto') : 'mute');
|
||||
|
||||
// 画布上的选中态:diagram-js 给每个形状挂了 data-element-id,外挂一个类,不改画布件
|
||||
useEffect(() => {
|
||||
@@ -680,7 +655,7 @@ function FlowLab({
|
||||
nodeId: n.id,
|
||||
label: n.label,
|
||||
activityId: n.activityId,
|
||||
owner: n.owner,
|
||||
// 契约不带负责人岗位 —— 分派归运营,依赖文件只描述干什么
|
||||
// 编排上手写的注记(给人读)
|
||||
declaredIn: n.in ?? [],
|
||||
declaredOut: n.out ?? [],
|
||||
@@ -825,7 +800,7 @@ function FlowLab({
|
||||
<FlowFigureBlock
|
||||
key={`${pkg.id}@${pkg.version}`}
|
||||
title={`${pkg.name} v${pkg.version}`}
|
||||
oneLiner={`蓝 = 活动点 · 琥珀 = 审批点 · 红 = 卡点`}
|
||||
oneLiner={`蓝 = 活动点 · 琥珀 = 审批点 · 线标 = 边号`}
|
||||
graph={graph}
|
||||
drilledIds={nodes.filter((n) => n.kind === 'activity').map((n) => n.id)}
|
||||
/*
|
||||
@@ -835,6 +810,12 @@ function FlowLab({
|
||||
*/
|
||||
canEdit
|
||||
onNodeClick={(nid) => { setSel(nid); setSelEdge(null); }}
|
||||
onEdgeClick={(edge) => {
|
||||
const i = (graph?.edges ?? []).findIndex(
|
||||
(x) => x.from === edge.from && x.to === edge.to && x.code === edge.code,
|
||||
);
|
||||
if (i >= 0) { setSelEdge(i); setSel(null); }
|
||||
}}
|
||||
onEditNodeText={async (nid, p2) => {
|
||||
setGraph((g) =>
|
||||
g === null
|
||||
@@ -949,22 +930,18 @@ function FlowLab({
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* 边拣选条:边属性 = 传递契约(条件 + 映射)—— 图只定顺序,契约在这儿填 */}
|
||||
{/* 边拣选条:边号立牌(E1、E2…),条件住属性面不上按钮 —— 图只定顺序,契约在属性里填 */}
|
||||
<div className="g-picker">
|
||||
{(graph?.edges ?? []).map((e, i) => {
|
||||
const toSem = calloutToOf(graph)?.get(e.to) ?? e.to;
|
||||
const brief = e.condition !== undefined && e.condition !== '' ? ` [${e.condition}]` : '';
|
||||
return (
|
||||
<button
|
||||
key={`${e.from}→${e.to}-${i}`}
|
||||
className={'g-pick t-mute' + (selEdge === i ? ' on' : '')}
|
||||
onClick={() => { setSelEdge(selEdge === i ? null : i); setSel(null); }}
|
||||
>
|
||||
<b className="g-mono">{e.from}→{toSem}</b>
|
||||
{(e.label ?? '') + brief}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{(graph?.edges ?? []).map((e, i) => (
|
||||
<button
|
||||
key={`${e.from}→${e.to}-${i}`}
|
||||
className={'g-pick t-mute' + (selEdge === i ? ' on' : '')}
|
||||
onClick={() => { setSelEdge(selEdge === i ? null : i); setSel(null); }}
|
||||
>
|
||||
<b className="g-mono">{e.code ?? `边${i + 1}`}</b>
|
||||
{e.from}→{e.to}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{node === null && selEdge === null ? (
|
||||
<Note>
|
||||
@@ -974,7 +951,6 @@ function FlowLab({
|
||||
) : node === null && selEdge !== null && graph !== null ? (
|
||||
<EdgeProps
|
||||
edge={graph.edges[selEdge]!}
|
||||
toSem={calloutToOf(graph)?.get(graph.edges[selEdge]!.to) ?? graph.edges[selEdge]!.to}
|
||||
onPatch={(f) => {
|
||||
setGraph((g) =>
|
||||
g === null ? g : { ...g, edges: g.edges.map((e, i) => (i === selEdge ? f(e) : e)) },
|
||||
@@ -1036,25 +1012,29 @@ function FlowLab({
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field
|
||||
k={node.flavor === 'approval' ? '审批岗位' : '责任岗位'}
|
||||
hint={node.flavor === 'approval' ? '审批单派给它' : '任务的负责人由它解析而来'}
|
||||
>
|
||||
<select
|
||||
className="g-select"
|
||||
value={node.owner ?? ''}
|
||||
onChange={(e) =>
|
||||
patch((n) => ({ ...n, owner: e.target.value === '' ? undefined : e.target.value }))
|
||||
}
|
||||
>
|
||||
<option value="">未指定 —— 保存前选一个岗位</option>
|
||||
{positions.map((q2) => (
|
||||
<option key={q2.id} value={q2.abbr}>
|
||||
{q2.abbr} · {q2.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
{node.flavor === 'approval' && (
|
||||
<Field k="审批岗位" hint="「卡到人的工位」是审批点的功能语义,审批单派给它">
|
||||
<select
|
||||
className="g-select"
|
||||
value={node.owner ?? ''}
|
||||
onChange={(e) =>
|
||||
patch((n) => ({ ...n, owner: e.target.value === '' ? undefined : e.target.value }))
|
||||
}
|
||||
>
|
||||
<option value="">未指定 —— 激活前选一个岗位</option>
|
||||
{positions.map((q2) => (
|
||||
<option key={q2.id} value={q2.abbr}>
|
||||
{q2.abbr} · {q2.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
{node.flavor !== 'approval' && (
|
||||
<Note>
|
||||
活动点<b>不带岗位</b> —— 谁来干是运营的事(分派机制未建前,运行时全员轮值、最闲者先)。
|
||||
</Note>
|
||||
)}
|
||||
{node.kind === 'activity' && node.flavor === 'approval' && (
|
||||
<Note>
|
||||
<b>审批点是系统内置要素</b>,不是活动 —— 声明即用、零开发、不进活动库(一个流程包里需要开发的
|
||||
@@ -1062,11 +1042,8 @@ function FlowLab({
|
||||
(声明式:回指定节点重开新实例 / 终止)· 催办阶梯。运行时它产出<b>审批单</b>而非任务。
|
||||
</Note>
|
||||
)}
|
||||
{node.owner === undefined && (
|
||||
<Note tone="warn">
|
||||
这个节点还没指定岗位 —— 不指定,起单时就不知道
|
||||
{node.flavor === 'approval' ? '审批单' : '任务'}该派给谁。
|
||||
</Note>
|
||||
{node.flavor === 'approval' && node.owner === undefined && (
|
||||
<Note tone="warn">这个审批点还没指定岗位 —— 不指定,起单时就不知道审批单该派给谁。</Note>
|
||||
)}
|
||||
</Prop>
|
||||
|
||||
@@ -1088,28 +1065,6 @@ function FlowLab({
|
||||
/>
|
||||
</Prop>
|
||||
)}
|
||||
{node.kind === 'callout' && (
|
||||
<Prop ico="rotate-ccw" title="去向" extra="失败走向 / 驳回去向">
|
||||
<Field k="回到节点">
|
||||
<select
|
||||
className="g-select"
|
||||
value={node.to ?? ''}
|
||||
onChange={(e) =>
|
||||
patch((n) => ({ ...n, to: e.target.value === '' ? undefined : e.target.value }))
|
||||
}
|
||||
>
|
||||
<option value="">未指定</option>
|
||||
{nodes
|
||||
.filter((n) => n.kind === 'activity')
|
||||
.map((n) => (
|
||||
<option key={n.id} value={n.id}>
|
||||
{n.id} · {n.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
</Prop>
|
||||
)}
|
||||
{node.kind === 'activity' && node.flavor !== 'approval' && (
|
||||
<Prop ico="boxes" title="链接活动实现" extra="装载面:编排不含实现,链接决定可运行">
|
||||
<Field k="实现" hint="活动住库;链好了这节点才算装载(激活判据之二)">
|
||||
|
||||
Reference in New Issue
Block a user