# ice-entity-designer **Repository Path**: ice-render/ice-entity-designer ## Basic Information - **Project Name**: ice-entity-designer - **Description**: An entity/relation designer based on ice-render. - **Primary Language**: TypeScript - **License**: MIT - **Default Branch**: master - **Homepage**: https://ice-render.github.io/ice-render-doc/ - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2022-03-28 - **Last Updated**: 2026-09-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
基于 ice-render 的可视化建模工具集:一套引擎承载 9 个域包 —— ER、流程图、BPMN 2.0、UML 类图、状态机、甘特、电力一次、电力二次、给水排水。
> 变更日志见 [CHANGELOG.md](./CHANGELOG.md)。 ## 1. 项目定位 IED(ice entity designer)是基于 [ice-render](https://github.com/ice-render/ice-render) 构建的**可视化建模工具集**:同一套引擎、同一套应用层机制(选择 / 增删改 / 连线 / 撤销重做 / 快照 / 语义校验 / 矢量导出)之上承载多个「域包」,每个域包 = **一个领域的记法 + 应用层 + 语义校验**。 现已落地 9 个域包: | 域包 | 图种 | 标准依据 / 互操作 | |---|---|---| | **ER**(默认) | 实体-关系模型 | 导出 TypeORM `EntitySchema` | | 流程图 | 起止 / 处理 / 判定 / 输入输出 | — | | BPMN 2.0 | 池 / 泳道 / 事件 / 网关 / 任务 | BPMN 2.0 XML 导入 + 导出(含 BPMNDI 布局) | | UML 类图 | 三段式类框 + 六种关系 | PlantUML / Mermaid 类图文本互操作 | | 状态机 | 伪状态 / 状态 / 复合状态容器 | PlantUML 状态图文本互操作 | | 甘特图 | 任务条 / 依赖线 / 关键路径 | Mermaid gantt 文本互操作 | | 电力一次系统图 | 单线图(23 种设备符号) | JB/T 5872-1991、GB/T 4728;电压一致 / 母线 T 接 / 五防校验 | | 电力二次回路 | 保护电流回路 + 端子排 | GB/T 4728.7、C37.2;回路编号 / 三相成组 / 端子号 / 接地校验 | | 给水排水工艺流程图 | 水厂 / 污水厂 AAO 主线 + 污泥线 | GB/T 50106 图例、GB 50014;工艺校验(进出线 / 介质管径 / 在线监测 / 污泥出路 / 内回流)+ 流径分析 | 默认域包 ER 以「节点 = 实体,连线 = 关系」组织数据模型,把画布上的设计结果序列化为符合 TypeORM `EntitySchema` 规范的 Schema,从而将「结构设计」与「实体类 / CRUD 代码生成」直接衔接;其它域包复用同一套交互闭环(选择、创建、更新、删除、关系连接、校验与导出),只在**记法**与**语义校验**上做区分。 > **引擎内核 `ice-render` 是 peer 依赖**:请与 `ice-entity-designer` 一起安装(npm 7+ 也会自动安装 peer)。 > 本包只 re-export 引擎,不再内联第二份内核,因此同一页面上的编辑器与其它 ICE 家族包共用同一个 `ICE` 实例、事件总线和类型注册表。 完整使用案例请参见: -
实体字段与约束标记:
关系语义:自引用、多对多、一对一:
交互式编辑器(右侧面板可直接切换到「TypeORM Schema」查看导出结果):
同一套内核也能承载**流程图**(`examples/flowchart-editor.html`):四类节点(开始/结束、处理、判定、输入/输出,
其中判定菱形与输入输出平行四边形是自定义 `ICEPath` 形状)、正交/贝塞尔连线 + 分支标签(是/否)、
拖拽 / 连线 / 撤销重做 / 快照存取:
再加一层业务记法就是 **BPMN 2.0**(`examples/bpmn-editor.html`):池 / 泳道真嵌套(拖动银行池,内部泳道、
任务和连线一起平移)、事件 / 网关 / 任务角标 / 数据对象 / 注释、顺序流 + 条件与默认流标记、
跨池的消息流,右侧面板按图元类型给出网关类型、事件种类、任务类型等属性,并内置语义校验与 BPMN 2.0 XML 导出:
同一套引擎继续承载 **UML 类图**(`examples/uml-editor.html`)—— 三段式类框、六种关系、继承成环校验,
以及 PlantUML / Mermaid 类图文本互操作:
**状态机**(`examples/statechart-editor.html`)—— 伪状态、普通状态、**复合状态容器**(拖动父容器时子状态跟随),
转移标签写作 `事件 [守卫] / 动作`:
**甘特图**(`examples/gantt-editor.html`)—— 时间轴与按天吸附、依赖线、自动排程与关键路径、
资源冲突校验、Mermaid gantt 文本互操作:
**电力一次系统图**(`examples/power-editor.html`)—— 110kV 双母线 + 10kV 单母线分段、69 台设备;
开关分合、带电分析与电压色标、五防相关校验、SVG / JSON 导出:
**电力二次回路**(`examples/secondary-editor.html`)—— 保护电流回路:CT 二次绕组 → 三相电流回路 →
端子排 → 保护装置,N 侧接地;端子排是真容器 —— 拖动跟随、快照往返不丢:
**给水排水工艺流程图**(`examples/water-editor.html`)—— 10 万 m³/d 市政污水厂 AAO 案例:
进水 → 格栅 → 曝气沉砂池 → 初沉池 → 厌氧 / 缺氧 / 好氧 → 二沉池 → 混凝沉淀 → 滤池 → 消毒 → 在线监测 → 排放,
再加混合液内回流、污泥回流与剩余污泥线(浓缩 → 脱水 → 外运)。管线按介质着色并标注管径,
右侧「工艺校验」查进出线 / 介质管径 / 在线监测 / 污泥出路 / 内回流,「流径分析」看关阀之后通不通:
## 4. 快速开始
```bash
npm install
npm run build
```
可运行的示例(`examples/` 下的页面加载上一级 `dist` 与本仓 `node_modules`,建议通过静态服务器打开):
| 示例 | 说明 |
|---|---|
| `examples/entity-editor.html` | 交互式编辑器:实时编辑字段、创建/删除实体与关系、校验与保存加载;右侧面板含「TypeORM Schema」标签页 |
| `examples/flowchart-editor.html` | 流程图编辑器:四类节点形状、拖拽、连线(含分支标签)、撤销重做、localStorage 存取与 JSON 导出;纯 DOM 面板,只依赖 `dist` 产物 |
| `examples/bpmn-editor.html` | BPMN 2.0 编辑器:信用卡申请审批案例(两个池 / 三条泳道)、八类图元、条件与默认流标记、语义校验、BPMN 2.0 XML 导入导出 |
| `examples/uml-editor.html` | UML 类图编辑器:三段式类框、六种关系、语义校验、矢量导出、PlantUML / Mermaid 文本互操作 |
| `examples/statechart-editor.html` | 状态机编辑器:伪状态 / 普通状态 / 复合状态容器、转移标签 `事件 [守卫] / 动作` |
| `examples/gantt-editor.html` | 甘特编辑器:时间轴与按天吸附、依赖线、自动排程、关键路径、资源冲突校验、矢量导出 |
| `examples/power-editor.html` | 电力一次系统图(单线图)编辑器:110kV 变电站案例(**110kV 双母线 + 10kV 单母线分段**两级电压,两回进线 / 两台主变 / 母联 / 母线 PT / 4 条 10kV 出线 / 电容器组 / 站用变,共 69 台设备),开关分合、带电分析与色标、五防相关校验 |
| `examples/water-symbols.html` | 给排水符号图例:38 种符号一页看全(位号在上 / 名称在下 / 图形居中),可导出 SVG 作交底或评审用 |
| `examples/water-editor.html` | 给水排水工艺流程图:市政污水厂 AAO 工艺(19 个符号 / 21 条管线),介质 + 管径标注、工艺校验(进出线 / 在线监测 / 污泥出路 / 内回流)、流径分析与阀门工况、矢量导出 |
| `examples/secondary-editor.html` | 电力**二次回路**(简化版):110kV 线路保护电流回路 —— CT 三个二次绕组 → 三相电流回路(A411/B411/C411 + N411)→ 端子排(201~204)→ 线路保护装置,N 侧接地;二次校验(回路编号 / 三相成组 / 端子号唯一 / 必须接地) |
| `examples/power-symbols.html` | 电力符号表:23 种一次设备符号(记法对齐 JB/T 5872-1991 与 GB/T 4728.1/3/4/6),可缩放平移、导出 SVG |
| [`ice-entity-designer-react-demo`](../ice-entity-designer-react-demo) | 独立的 React 集成示例工程(webpack + TypeScript),涵盖 ref / hook / onChange / 受控模式 |
```bash
python3 -m http.server 8899 # 然后访问 http://localhost:8899/examples/entity-editor.html
```
## 5. 使用方式
`EntityDesigner` 是 Entity / Relation 之上的轻量应用层,负责把建模交互闭环串起来:
```js
import { ICE, EntityDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const designer = new EntityDesigner(ice);
// 建模:创建实体与关系
const user = designer.createEntity({ entityName: 'User' });
const role = designer.createEntity({ entityName: 'Role' });
designer.createRelation({
sourceId: user.state.id,
targetId: role.state.id,
relationType: 'many-to-many',
joinTableName: 'user_roles',
linkShape: 'bezier', // 连线形态:'visio'(默认,正交折线)| 'bezier'(贝塞尔曲线)
});
// 导出与校验
const schema = designer.toSchemaObject(); // TypeORM Schema(对象)
const schemaText = designer.toSchemaString(); // TypeORM Schema(JSON 字符串)
const issues = designer.validate(); // 校验问题列表
// 项目存取与历史
const snapshot = designer.serializeProject();
const report = designer.loadProject(snapshot); // 非法 / 版本不兼容的快照会抛错,且不会改动当前项目与历史栈
// report = { loaded, entities, relations, unknownTypes, skipped }
designer.undo();
```
#### 5.1 项目快照契约
- 快照带 `schemaVersion`(当前 `1`)与每个节点的 `typeId`;载入时**按 `typeId` 分派构造函数**(走 ICE 注册表,下游 `ice.registerType()` 注册的领域图元同样可载入)。旧快照没有 `typeId` 时,按所在数组归位(`entities[]` → `Entity`,`relations[]` → `Relation`)。
- 快照带 `createTime`(ISO 8601 UTC,如 `2026-09-13T07:15:45.655Z`)= 这份项目**首次创建**的时刻:
首次写出即定下并记在 `ice.documentMeta` 上(因此同一会话反复 `serializeProject()` 结果稳定,undo/redo 的快照回放依赖这一点),
载入别人的快照时读回来,于是「打开 → 编辑 → 保存」不会被改写;缺失 / 脏值(例如旧的
`2022/1/1 00:00:00`)会归一化成 ISO 或回退到当前时刻。**没有 `lastModifyTime`**——每次写出都会变,
留着会破坏「两次序列化结果相同」的契约。
- **`typeId` 一律是 `namespace:Type` 格式**(2026-09-13 起):本包的领域图元统一用 `ice-entity-designer:*`
(`ice-entity-designer:Entity`、`ice-entity-designer:FlowNode`、`ice-entity-designer:GanttTask`…),
与引擎内置的 `ice-render:*`、图表的 `ice-chart:*` 分属不同命名空间,因此**跨包不会撞名**。
注册走 `registerIEDType()`(`src/utils/type-registry.ts`);**不做旧名兼容**(家族仍在发布初期),
旧快照里的 `Entity`、`FlowNode` 之类无 namespace 值会被当作未注册类型跳过并记入 `report.unknownTypes`。
判型不要拿字面量与 `typeId` 比:用 `x instanceof FlowNode` 或 `selected.constructor.typeId === FlowNode.typeId`。
- **容错加载**:遇到未注册的 `typeId` 只跳过该节点并记录(`report.unknownTypes` / `report.skipped`),不会让整份数据打不开——与引擎 `Deserializer` 的语义一致。
- **自洽保证**:`serializeProject()` 的产物永远能通过 `loadProject()` 的结构校验(结构契约见 `src/utils/project-snapshot.schema.json`);载入失败时当前项目与 `undo`/`redo` 栈都不会被改动。
- **唯一字段定义**:快照写什么、校验查什么,都由 `src/utils/project_codec.ts` 的一份定义驱动(不再 snapshot 一份、validator 一份)。新增 state 字段却忘了登记时,`tests/designer/codec-completeness.test.ts` 会以「未覆盖的 state 键」直接报红。
- **自定义 JSON 透传**:应用层把业务元数据挂在 `node.state.data` 上即可,它会原样写进快照并在载入时回填(与引擎序列化对 `state` 的处理一致)。
也支持更底层的组件式用法:
```js
const schema = IED.toSchemaObject(ice.childNodes);
const schemaText = IED.toSchemaString(ice.childNodes);
```
导出的对象可直接构造 TypeORM 实体:
```js
import { EntitySchema } from 'typeorm';
const schemas = designer.toSchemaObject().map((obj) => new EntitySchema(obj));
```
#### 5.2 流程图(FlowDesigner)
包内除 ER 之外还内置了一套**流程图**领域图元与应用层(同一个 `ice` 实例即可承载):
```js
import { ICE, FlowDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const flow = new FlowDesigner(ice);
const start = flow.createNode('terminator', { title: '开始' });
const check = flow.createNode('decision', { title: '库存充足?' });
flow.createEdge({ sourceId: start.state.id, targetId: check.state.id, sourcePort: 'B', targetPort: 'T' });
flow.fitViewport(); // 适应视图
flow.serialize(); // 流程图快照(version / kind / nodes / edges)
flow.undo(); // 100 步历史
```
| 能力 | API |
|---|---|
| 节点类型 | `createNode('terminator' \| 'process' \| 'decision' \| 'io', props)`;预设尺寸 / 配色见 `FLOW_NODE_KINDS` |
| 连线 | `createEdge({ sourceId, targetId, sourcePort, targetPort, label, linkShape })`;插槽位置 `T/R/B/L/C`,节点拖动时连线自动跟随 |
| 样式 | 节点:`fillColor` / `strokeColor` / `textColor` / `fontSize`(`updateNode` 即时生效);连线:`style.strokeStyle`(线色,同时作为箭头填充)/ `style.lineWidth`、`labelStyle.fillStyle`(标签颜色),全部随快照存取 |
| 增删改查 | `nodes` / `edges` / `selected` / `select()` / `updateNode()` / `updateEdge()` / `remove()`(删节点级联删连线)/ `clear()` |
| 历史与快照 | `undo()` / `redo()` / `canUndo()` / `canRedo()`、`serialize()` / `toSnapshot()` / `load()`(返回 `{ loaded, nodes, edges, skipped }`)。文档 **v2 直接复用引擎的序列化机制**:`{ version: 2, kind: 'flowchart', scene: <引擎 Serializer 产物> }`,因此自定义 `data` 与任何新增 state 字段自动往返;v1(`nodes`/`edges` 数组)仍可读,导出统一为 v2 |
| 导出 | `toSvg(options)` —— 导出**矢量** SVG(放大不糊、可进设计工具/打印);与画布同一口径 |
| 视图与订阅 | `fitViewport(padding)`、`subscribe()`、`dispose()` |
| 程序化高亮 | `setHighlights(ids, options)` / `highlight(id)` / `clearHighlights()` / `getHighlightedIds()` —— 给"指着讲"、教程、演示用。描边环落在**工具层**:**不进快照**、**不参与命中**、图元被拖动或删除时**自动跟随**;默认色取引擎主题主色,可传 `{ color, lineWidth, padding, radius, fill }` 覆盖(`fill` 默认关:工具层整体画在图元之上,填充会盖住位号与名称) |
自定义形状(判定菱形 / 输入输出平行四边形)在 `src/flow/flow_shapes.ts`,走的是引擎的 `ICEPath` 子类机制。
流程图节点是**复合组件**(形状 + 标题由 kind/标题/配色派生):它们实现了引擎的 `hasDerivedChildren()`,
内部子组件不写进文档、载入时由构造函数按 state 重建——避免重复挂载,也让同一份数据的两次序列化结果保持一致。
可运行的完整示例见 `examples/flowchart-editor.html`;React 用法见 [6.6](#66-流程图的-react-绑定);
AI Agent 生成流程图的 JSON DSL 见 `ice-entity-designer-dsl`。
#### 5.3 BPMN 2.0(`BpmnDesigner`)
`BpmnDesigner` 继承 `FlowDesigner`,只补 BPMN 特有的事:顺序流上的条件 / 默认流标记(派生装饰,
放在工具层、不进文档)与语义校验。其余能力(建节点 / 连线、选择、增删改、撤销重做、快照、适应视图、订阅)全部沿用:
```js
import { ICE, BpmnDesigner, toBpmnXml, fromBpmnXml } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const bpmn = new BpmnDesigner(ice);
ice.alignmentGuide.enable({ threshold: 6 }); // 引擎自带的对齐标尺,BPMN 场景同样开启
// 池 / 泳道也是节点;节点按几何**自动嵌进最内层容器**(泳道优先于池)
const bank = bpmn.createNode('bpmnPool', { title: '银行', left: 60, top: 60, width: 1180, height: 340 });
bpmn.createNode('bpmnLane', { title: '受理岗', left: 60, top: 92, width: 1180, height: 150 });
const submit = bpmn.createNode('bpmnEvent', { title: '申请提交', eventKind: 'start', left: 240, top: 120 });
const verify = bpmn.createNode('bpmnTask', { title: '身份核验', taskType: 'service', left: 400, top: 100 });
const gateway = bpmn.createNode('bpmnGateway', { title: '是否通过', gatewayType: 'exclusive', left: 880, top: 255 });
bpmn.createEdge({ sourceId: submit.state.id, targetId: verify.state.id, label: '受理' });
bpmn.createEdge({ sourceId: verify.state.id, targetId: gateway.state.id, condition: '评分 >= 600', isDefault: true });
bpmn.validateBpmn(); // BPMN 语义问题列表(每个池一个开始事件、顺序流不跨池…)
const xml = toBpmnXml(bpmn); // BPMN 2.0 XML + BPMNDI 布局
const report = fromBpmnXml(xml, bpmn); // 导入并重建(含池 / 泳道容器)
```
| 能力 | API |
|---|---|
| 节点类型 | `createNode('bpmnEvent' \| 'bpmnTask' \| 'bpmnGateway' \| 'bpmnSubprocess' \| 'bpmnDataObject' \| 'bpmnAnnotation' \| 'bpmnPool' \| 'bpmnLane', props)`;预设见 `FLOW_NODE_KINDS` |
| 语义属性 | 事件 `eventKind`(start / intermediate / end)+ `trigger`;网关 `gatewayType`;任务 / 子流程 `taskType` —— `updateNode()` 改完立即重建形状与角标 |
| 连线 | `createEdge({ sourceId, targetId, flowType: 'sequence' \| 'message' \| 'association', label, condition, isDefault, linkShape })`;线型与箭头由 `flowType` 派生 |
| 容器 | 池 `bpmnPool`(顶部 32px 标题带,标题横排)、泳道 `bpmnLane`(左侧 32px 标题带,标题**旋转 -90° 竖排**、居中,过长按名称带截断加省略号);建节点时按几何自动嵌套,拖动容器时内部图元与连线一起走 |
| 校验与互操作 | `validateBpmn()`、`toBpmnXml(designer)`、`fromBpmnXml(xml, designer)` |
| 其余 | 与 `FlowDesigner` 完全相同:`nodes` / `edges` / `select()` / `updateNode()` / `updateEdge()` / `remove()` / `undo()` / `redo()` / `serialize()` / `load()` / `fitViewport()` / `subscribe()` |
BPMN 节点同样是**复合组件**(形状 + 角标 + 标记由 state 派生),内部子组件不写进文档、载入时重建。
AI Agent 生成 BPMN 的 JSON DSL(`kind: 'bpmn'`)见 `ice-entity-designer-dsl`。
#### 5.4 导出:矢量 SVG(与画布同一口径)
画布的 `toDataURL()` 是**光栅快照**(分辨率写死、放大就糊)。需要出图给文档、打印或设计工具时用
**矢量导出** —— 它复用引擎的 `exportSvg()`,从组件树 + 路径命令流重新生成 SVG,与画布逐像素同一口径
(绘制顺序、世界矩阵、样式合并、透明度、祖先裁剪、虚线、渐变、阴影、连线标签):
```js
// 流程图 / BPMN(应用层,FlowDesigner 与 BpmnDesigner 都有)
const svg = designer.toSvg(); // 内容自适应 + 透明背景
const svg = designer.toSvg({ background: '#ffffff', padding: 16 });
const svg = designer.toSvg({ area: 'viewport' }); // 当前视口所见即所导
// 任何场景(ER / 流程图 / BPMN 都能用,含 `{ svg, width, height }` 版本)
const svg = IED.exportSvg(ice, { scale: 2 });
const { svg, width, height } = IED.exportSvgResult(ice, { padding: 12 });
```
`examples/bpmn-editor.html` 与 `examples/flowchart-editor.html` 上都有「导出 SVG」按钮,点一下即可下载
(BPMN 案例导出的池/泳道/事件/网关/连线/标签都是矢量)。服务端出图见引擎的 `ICE.headless()`。
限制(与引擎一致):阴影用 `feDropShadow` 近似(模糊观感不会与画布逐像素相同);SVG 与 canvas 的
字形栅格化是两套实现,文字位置**对齐口径一致、逐像素允许微差**;导出的是**静态瞬间**(蚂蚁线动画
只保留当前相位)。
#### 5.5 甘特图(`GanttDesigner`)
排期场景:横轴是**时间**(`start` 日期 × 持续天数 × 每日像素)、纵轴是行,任务条**按天吸附**拖动,
依赖线从「前置任务的结束」指向「后置任务的开始」。
```js
import { ICE, GanttDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const gantt = new GanttDesigner(ice);
const review = gantt.createTask({ title: '需求评审', start: '2026-03-02', days: 4, progress: 1 });
const design = gantt.createTask({ title: '交互设计', start: '2026-03-05', days: 6, progress: 0.8 });
gantt.createDependency({ sourceId: review.state.id, targetId: design.state.id });
gantt.setDayWidth(36); // 时间轴缩放:所有任务与依赖一起重排
gantt.validateGantt(); // 依赖成环 / 进度越界 / 持续天数非法
const svg = gantt.toSvg({ background: '#ffffff' });
```
与其它域包同一套机制:任务条是复合组件(条 + 进度覆盖 + 文字由 state 派生)、依赖复用引擎折线
(插槽吸附 / 正交路由 / 跟随宿主)、快照与矢量导出全部继承。两条甘特特有的能力:
| 能力 | 说明 |
|---|---|
| 时间轴 | `dayWidth` / `originDate` / `labelColumnWidth` 统一换算;框架(左列任务名 + 日期刻度 + 行线)由派生的 `GanttRuler` 渲染,模型一变就重建 |
| 按天吸附 | `GanttTask.setPosition()` 把 x 吸附到整天的格子并反推 `start`(排期不会出现「13:47 开工」) |
可运行示例:`examples/gantt-editor.html`(移动端 2.0 发布排期,含依赖、进度、**自动排程**与**关键路径**按钮)。
文本互操作:`IED.toMermaidGantt(designer)` / `IED.fromMermaidGantt(text, designer)` 走 Mermaid gantt 语法子集 ——
`section` 对应负责人(`resource`),单前置依赖写成 `after`(Mermaid 自己画依赖箭头);
多前置、或带 buffer 的排期写成显式日期 + `%% task` 注释(Mermaid 只忽略注释,渲染不受影响)。
#### 5.6 BPMN 令牌仿真(`BpmnSimulator`)
「流程怎么走」可以直接演示出来:令牌从开始事件出发,沿顺序流前进、在任务上停留、在排他网关选一条分支、
在并行网关一分为多,到达结束事件后消失。
```js
import { BpmnSimulator } from 'ice-entity-designer';
const simulator = new BpmnSimulator(bpmn, { nodeDuration: 500, edgeDuration: 700 });
simulator.start(); // 每个开始事件一个令牌;浏览器里由引擎帧事件驱动
simulator.step(50); // 也可以手动推进(测试/单步调试用,确定性)
simulator.stop(); // 清空令牌
```
| 能力 | 说明 |
|---|---|
| 令牌 | 工具层组件(`ice.toolNodes`):**不进文档、不影响快照与 BPMN XML 导出**,停止即干净退场 |
| 路由 | 令牌位置在连线的**实际折点**上按弧长插值,所以始终贴在画出来的线上(含正交绕线) |
| 语义 | 排他网关优先走带 `condition` 的流、其次走非默认流;并行/包容网关分裂成多条令牌;结束事件上令牌消亡 |
| 推进 | `step(dtMs)` 显式推进(测试可断言);`start()` 后自动挂帧循环 |
可运行示例:`examples/bpmn-editor.html` 的「仿真 / 停止」按钮(案例是信用卡申请审批)。
#### 5.7 UML 类图(域包示例)
UML 是**域包(domain pack)**的第一个完整示例:形状 + 应用层 + 语义校验,其余(选择/增删改/连线/
撤销重做/快照/适应视图/矢量导出)全部沿用引擎与 `FlowDesigner`。
```js
import { ICE, UmlDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const uml = new UmlDesigner(ice);
const entity = uml.createClass({ kind: 'class', className: 'Entity', abstract: true, methods: ['+ save(): void'] });
const user = uml.createClass({ className: 'User', attributes: ['- email: string'], methods: ['+ placeOrder(): Order'] });
const payable = uml.createClass({ kind: 'interface', className: 'Payable', methods: ['+ pay(amount: number): void'] });
uml.createRelation({ sourceId: user.state.id, targetId: entity.state.id, relationKind: 'inheritance' });
uml.createRelation({ sourceId: payable.state.id, targetId: user.state.id, relationKind: 'realization' });
uml.validateUml(); // 重名类 / 悬空关系 / 继承成环
const svg = uml.toSvg({ background: '#ffffff', padding: 16 });
```
| 记法 | 线型 + 端点标记 |
|---|---|
| `inheritance` 继承 | 实线 + 空心三角(指向父类) |
| `realization` 实现 | 虚线 + 空心三角(指向接口) |
| `association` 关联 | 实线 |
| `aggregation` 聚合 | 实线 + **空心菱形**(整体一侧) |
| `composition` 组合 | 实线 + **实心菱形**(整体一侧) |
| `dependency` 依赖 | 虚线 + 开放箭头 |
类框是**三段式**(类名 / 属性 / 方法):成员是自由文本(`- id: string`、`+ pay(): void`),
可见性/静态/泛型都由文本表达 —— 与 PlantUML/Mermaid 的通行写法一致,AI 生成不必学另一套结构化语法;
接口与枚举带构造型,抽象类标 «abstract»;**框高随成员自动增长**,成员不会被画到框外。
可运行示例:`examples/uml-editor.html`(电商支付的类模型:继承 / 实现 / 组合 / 关联 / 依赖)。
文本互操作:`IED.toPlantUml(designer)` / `IED.fromPlantUml(text, designer)` 走 PlantUML / Mermaid
类图语法子集(三段式类框 + 六种关系的连接符),导出可直接贴进 Wiki / Markdown / 代码评审。
#### 5.8 状态机(`StatechartDesigner`)
状态机是**域包(domain pack)**的第二个完整示例:伪状态(初始 / 终止)、普通状态、
**复合状态是容器**(内部可放子状态,拖动父状态子状态跟着走),转移标签是 `事件 [守卫] / 动作`。
```js
import { ICE, StatechartDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const statechart = new StatechartDesigner(ice);
const initial = statechart.createState({ kind: 'initial', left: 120, top: 120 });
const pending = statechart.createState({ title: '待支付', left: 240, top: 100 });
const paid = statechart.createState({ title: '已支付', left: 620, top: 100 });
statechart.createTransition({ sourceId: initial.state.id, targetId: pending.state.id });
statechart.createTransition({
sourceId: pending.state.id,
targetId: paid.state.id,
event: '支付成功',
guard: '金额 > 0',
action: '生成订单',
});
statechart.validateStatechart(); // 缺初始 / 终止有出边 / 孤立状态 / 从初始不可达
const svg = statechart.toSvg({ background: '#ffffff' });
```
文本互操作:`IED.toPlantUmlState(designer)` / `IED.fromPlantUmlState(text, designer)` 走 PlantUML 状态图语法子集 ——
伪状态映射成 `[*]`,复合状态成 `state 订单处理 { ... }` 嵌套块(块的嵌套就是容器归属),
转移标签导入时拆回事件 / 守卫 / 动作三段(`IED.splitTransitionLabel(label)` 是这一步的公开口径)。
可运行示例:`examples/statechart-editor.html`(订单状态机,含复合状态与 PlantUML 导入导出)。
#### 5.9 电力一次系统图(单线图)
面向电力行业的第一块垂直切片:**一次设备符号库 + 应用层 + 拓扑 + 语义校验**。
记法对齐 **JB/T 5872-1991《高压开关设备电气图形及文字符号》**(QF 断路器 / QS 隔离开关 /
QL 负荷开关 / QE 接地开关 / TA 电流互感器 / TV 电压互感器 / TM 变压器 / FU 熔断器 / F 避雷器 /
L 电抗器 / E 接地),通用规则遵循 GB/T 4728(等同 IEC 60617)。符号依据与默认口径见
`docs/power-symbol-spec.md`。
```js
import { ICE, PowerDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const power = new PowerDesigner(ice);
const line = power.createSymbol('generator', { name: '线路1', voltageLevel: '110kV' });
const qf = power.createSymbol('breaker', { name: '1101', voltageLevel: '110kV' });
const bus = power.createSymbol('busbar', { name: '#1M', voltageLevel: '110kV', width: 720 });
power.createLine({ sourceId: line.state.id, targetId: qf.state.id });
power.createLine({ sourceId: qf.state.id, targetId: bus.state.id });
power.setEnergizedSource(line.state.id, true); // 标电源点
power.setSwitchState(qf.state.id, 'closed'); // 运行态:合闸
power.applyTopology(); // 带电分析 → 写回各设备,供色标使用
power.validatePower(); // 编号唯一 / 电压等级一致 / 母线进线 / 断路器两侧隔离开关 / 五防
const svg = power.toSvg({ background: '#ffffff' });
```
| 能力 | 说明 |
|---|---|
| 符号库 | 15 种一次设备;每个派生部件带稳定 `role`(blade / arcMark / contactBar / winding / coil…),测试按 role 认记法 |
| 电压等级色标 | `setVoltageColors({ '110kV': '#xxxxxx' })` 覆盖;默认值见规格文档 |
| 运行态 | `setSwitchState(id, 'open' / 'closed')`:刀臂形状 + 分合标签 + 带电范围一起更新 |
| 拓扑 | `topology()` 返回带电设备与电气连通域;`applyTopology()` 把带电状态写回节点(不带电自动变灰) |
| 记法不可变换 | 所有符号 `transformable: false`:**只能拖动**,没有缩放/旋转手柄(尺寸与朝向是记法的一部分);母线长度、柜体宽高在属性面板里用数值改;图纸整体缩放走滚轮视图缩放 |
| 母线 T 接 | `attachToBus(device, bus, { centerX })`:设备记 `attachedBusId` 并把顶部引线贴住母线(隐式等电位,不用画绕行导体);`detachFromBus()` / 拖离几何范围即断开;拖动母线时挂上去的间隔整体跟随 |
| 语义校验 | 设备编号唯一、直接相连的电压等级一致(变压器两侧例外)、母线要有进线、断路器两侧应有隔离开关,以及**带电合接地刀闸 / 带接地线合闸送电**这两条五防相关规则 |
可运行示例:`examples/power-editor.html`(110kV 变电站:双母线 + 母联 + 两回进线 + 两台主变);
符号表页:`examples/power-symbols.html`。
#### 5.10 电力二次回路(简化版)
一次图是**单线图**,二次图是**回路图**:一条线 = 一个具体回路,线上标**回路编号**(A411/B411/C411/N411)、
端子带**端子号**(201…)、电缆带**电缆编号**(1D1…)。记法与范围见 `docs/power-secondary-spec.md`
(图种清单、IEEE C37.2 功能编号对照、来源)。
```js
import { ICE, SecondaryDesigner } from 'ice-entity-designer';
const ice = new ICE().init('canvas-1');
const secondary = new SecondaryDesigner(ice);
const winding = secondary.createSymbol('ctWinding', { name: '1LHa' });
const { strip, terminals } = secondary.createTerminalStrip({ title: '1D 端子排', terminals: [{ no: '201' }, { no: '202' }] });
const device = secondary.createSymbol('relayDevice', { name: '线路保护', tag: 'RCS-941A' });
secondary.createWire({ sourceId: winding.state.id, targetId: terminals[0].state.id, circuitNo: 'A411', cableNo: '1D1' });
secondary.createWire({ sourceId: terminals[0].state.id, targetId: device.state.id, circuitNo: 'A411' });
secondary.validateSecondary(); // 回路编号 / 三相成组 / 端子号唯一 / 必须接地
const svg = secondary.toSvg({ background: '#ffffff' });
```
| 能力 | 说明 |
|---|---|
| 元件库 | 常开/常闭接点、按钮、切换开关、压板、信号灯、保护装置方框、互感器二次绕组、端子、接地(记法按 GB/T 4728.7,文字符号用 C37.2 功能编号) |
| 端子排 | 容器:端子是真实子节点 —— 拖动端子排端子跟着走,端子各自可接线,**快照往返不丢端子** |
| 回路编号 | `createWire({ circuitNo })`:线就是回路,编号画在线上;电缆编号是数据字段 |
| 二次校验 | 导线必须有回路编号、三相电流回路编号成组(缺相报错)、端子号唯一、二次回路必须接地 |
可运行示例:`examples/secondary-editor.html`(110kV 线路保护电流回路,简化版)。
## 6. 在 React 中使用
包内置 React 绑定(子路径导出 `ice-entity-designer/react`),不需要自己写 ref / effect 胶水代码。
```bash
npm install ice-entity-designer ice-render react react-dom
```
```tsx
import { useRef } from 'react';
import { EntityDesignerCanvas, useEntityDesigner } from 'ice-entity-designer/react';
import type { EntityDesignerHandle } from 'ice-entity-designer/react';
// 子树内可以取到同一个 EntityDesigner 实例
function Stats() {
const designer = useEntityDesigner();
return {designer ? `${designer.entities.length} 个实体` : '初始化中…'};
}
export default function App() {
const ref = useRef