forked from ChaiHao/BPOS-MVP
活动契约不带负责人岗位(用户定): - 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
147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
/**
|
|
* 编排画布 —— diagram-js 画布件的宿主(编码规范 §6.1「PRD 画布件 1:1 平移」)。
|
|
*
|
|
* `FlowFigureBlock` 自原型 `HostExample` 平移:区块小标题右侧「编辑」芯片进手绘编辑器
|
|
* (FlowEditor,画布内工具栏含 取消/保存绘图);展示态 hero/cap 框 + FlowDiagram
|
|
* (单击节点下钻/选中、双击活动就地改文字);图例贴在流程图正下方。
|
|
* 必须包在 `.process-desk > .process-doc` 纸面容器内(flow-canvas.css 规则 scoped 到它)。
|
|
*
|
|
* `FlowFigure` 保留为只读便捷壳(任务页的流程图用它)。
|
|
*/
|
|
import { useState } from 'react';
|
|
import type { ProcessPackage } from '@contracts';
|
|
import { Ico } from '@ui';
|
|
import { FlowDiagram } from './FlowDiagram.tsx';
|
|
import { FlowEditor } from './FlowEditor.tsx';
|
|
import type { FlowEdge, FlowGraph } from './flow-layout.ts';
|
|
import type { SpPosition } from './support.tsx';
|
|
import { packageToGraph } from './pkg-to-graph.ts';
|
|
import type { InstanceMark } from './pkg-to-graph.ts';
|
|
import './flow-canvas.css';
|
|
|
|
export function FlowFigureBlock({
|
|
title,
|
|
oneLiner,
|
|
graph,
|
|
drilledIds,
|
|
positions,
|
|
canEdit,
|
|
onNodeClick,
|
|
onEdgeClick,
|
|
onEditNodeText,
|
|
onSaveFlowGraph,
|
|
}: {
|
|
title: string;
|
|
oneLiner?: string | undefined;
|
|
graph: FlowGraph | null;
|
|
/** 已建立下钻内容的节点 id(删除这些节点时编辑器会二次确认) */
|
|
drilledIds: string[];
|
|
positions?: SpPosition[] | undefined;
|
|
canEdit: boolean;
|
|
onNodeClick: (nodeId: string) => void;
|
|
/** 点击连线选中边(属性卡随之切到该边);不传则边不可点 */
|
|
onEdgeClick?: ((edge: FlowEdge) => void) | undefined;
|
|
/** 双击流程图活动就地改文字(标题 / 主办) */
|
|
onEditNodeText?: ((nodeId: string, patch: { label: string; owner?: string }) => Promise<void>) | undefined;
|
|
/** 手工绘图:整图保存(idMap = 本次编辑会话 原id→现id,宿主用它重键下钻内容) */
|
|
onSaveFlowGraph?: ((graph: FlowGraph, idMap: Record<string, string>) => Promise<void>) | undefined;
|
|
}) {
|
|
const [editing, setEditing] = useState(false);
|
|
return (
|
|
<>
|
|
<div className="view-lbl">
|
|
流程图 · 顺序
|
|
{canEdit && onSaveFlowGraph !== undefined && !editing && (
|
|
<button
|
|
type="button"
|
|
className="dc2-addbtn"
|
|
title="手工绘图(珊格正交:活动点/审批点/起止,拖拽连线);画布内工具栏有 取消/保存绘图"
|
|
onClick={() => setEditing(true)}
|
|
>
|
|
<Ico name="pen-line" s={11} /> 编辑
|
|
</button>
|
|
)}
|
|
</div>
|
|
{editing && onSaveFlowGraph !== undefined ? (
|
|
<div className="hero">
|
|
<div className="cap">
|
|
<span>FIG — {title}流程 · 手工绘图</span>
|
|
<span>珊格 10px · 直线与直角折线</span>
|
|
</div>
|
|
<FlowEditor
|
|
initial={graph}
|
|
drilledIds={drilledIds}
|
|
positions={positions}
|
|
onSave={async (next, idMap) => {
|
|
await onSaveFlowGraph(next, idMap);
|
|
setEditing(false);
|
|
}}
|
|
onCancel={() => setEditing(false)}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="hero">
|
|
<div className="cap">
|
|
<span>FIG — {title}流程</span>
|
|
<span>{oneLiner ?? '活动 · 网关 · 顺序流'}</span>
|
|
</div>
|
|
<div className="fig-body">
|
|
<FlowDiagram
|
|
graph={graph}
|
|
variant="business"
|
|
onNodeClick={onNodeClick}
|
|
onEdgeClick={onEdgeClick}
|
|
onEditNode={canEdit ? onEditNodeText : undefined}
|
|
ownerOptions={positions}
|
|
ariaLabel={`${title}业务流程图`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 图例贴在流程图正下方,图标与绘图工具(元素盘)同款 SVG */}
|
|
<div className="legend">
|
|
<span>
|
|
<b>图例</b>
|
|
</span>
|
|
<span><i className="lg-i fe-pi-start" aria-hidden /> 起始</span>
|
|
<span><i className="lg-i fe-pi-end" aria-hidden /> 结束</span>
|
|
{/* 节点只分活动点/审批点 —— 标准/案例是实现方式,不进编排面的词汇表 */}
|
|
<span><i className="lg-i fe-pi-sop" aria-hidden /> 活动点</span>
|
|
<span><i className="lg-i fe-pi-approval" aria-hidden /> 审批点</span>
|
|
<span><i className="lg-i lg-flow" aria-hidden /> 顺序流</span>
|
|
<span className="sep">|</span>
|
|
<span>
|
|
<b>责任</b>
|
|
</span>
|
|
<span>右上角标 责任人</span>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
/** 只读壳:流程包 → 图 → 画(任务页 / 工作流实例页用) */
|
|
export function FlowFigure({
|
|
pkg,
|
|
instances,
|
|
onNodeClick,
|
|
}: {
|
|
pkg: ProcessPackage;
|
|
instances: readonly InstanceMark[];
|
|
onNodeClick?: (nodeId: string) => void;
|
|
}) {
|
|
const graph = packageToGraph(pkg, instances);
|
|
return (
|
|
<div className="process-desk">
|
|
<div className="process-doc">
|
|
<FlowDiagram
|
|
graph={graph}
|
|
variant="business"
|
|
ariaLabel={`流程图 · ${pkg.name}`}
|
|
{...(onNodeClick === undefined ? {} : { onNodeClick })}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|