ice-entity-designer
v0.4.3
Published
An entity designer based on ice-render.
Downloads
202
Readme
1. 项目定位
IED(ice entity designer)是基于 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实例、事件总线和类型注册表。
完整使用案例请参见:
2. 核心能力
本节以默认域包 ER 为例展开;其余域包的能力见第 5 节「使用方式」与 examples/ 下各自的示例页。
可视化建模
- 拖拽编辑实体:实体名、字段类型、长度、默认值、注释。
- 字段级约束标记:
PK/FK/UQ/AI/NN,并支持index。 - 实体表头、字段文本、分隔线样式均可配置(
headerStyle/fieldStyle/dividerStyle)。
关系表达
- 覆盖四种关系:
one-to-one/one-to-many/many-to-one/many-to-many。 - 连线形态可切换:
linkShape: 'visio' | 'bezier'(默认visio)。Visio 是引擎的正交折线(出口点 + 路径评分), 贝塞尔是沿插槽法线出/入的三次曲线。编辑器里改连线形态有两个入口:选中连线后用关系属性面板的「连线形态」下拉, 或用工具栏的同名下拉 —— 选中连线时它直接改这条线,未选中时它同时作为新建连线的默认形态。 - 支持自引用关系,并可指定
parent/children属性名。 - 关系语义完整:
onDelete/onUpdate/joinTableName,以及自定义两端基数(如1 : 0..N)。 - 连线标签自动呈现基数与约束(如
1 : 1 ON DELETE CASCADE)。
导出与校验
- 一键导出 TypeORM Schema:
toSchemaObject()/toSchemaString()输出符合 TypeORMEntitySchema规范的普通对象,可直接new EntitySchema(obj)使用。 - 关系外键归属自动判定:
one-to-many/one-to-one的外键在目标侧、many-to-one的外键在源侧,joinColumn自动落在持有外键的一端;many-to-many生成joinTable并在反向侧补全inverseSide。 - 列类型规范化:
number → int、string → varchar、boolean → boolean、decimal(12,2) → precision/scale;非字符串类型不保留length。 - 内置 Schema 校验:重名实体、重复字段、未命名字段、悬空关系、
many-to-many缺少joinTableName等。 - 双向关系补全:按关系类型自动补全反向属性与
inverseSide,many-to-many自动生成复数形式的集合属性。
编辑器内核能力(继承自 ice-render)
- 画布滚轮缩放、空白处拖拽平移。
- 每个域包示例页统一接入上面这套视口交互(
examples/canvas-interactions.js):canvas 铺满可视区, 滚轮以光标为锚点缩放,空白处左键 / 任意位置中键拖拽平移,工具栏带「适应视图 / 复位视图」。 - 记法不可变换:所有域包的图元与连线一律
transformable: false—— 只允许拖动与点选, 不提供缩放/旋转/斜切手柄(尺寸与朝向是记法的一部分);需要变尺寸的元素(母线长度、BPMN 池/泳道、 柜体宽高、流程图节点尺寸…)在属性面板里用数值改,甘特条宽度则由「天数 × 每日像素」推出。 图纸整体缩放走滚轮(视图缩放),与图元缩放严格分开。 - 拖拽对齐引导线与磁吸(默认开启):图元位置就是数据、没有布局能约束它,缺了引导必然越拖越乱
("看着对齐了、其实差 3px"),所以设计器在构造时就调用
ice.alignmentGuide.enable({ threshold: 6 }): 拖动时出对齐提示线,并按边缘 / 中心 / 等间距三类候选吸附。想调阈值或关掉:ice.alignmentGuide.setOptions({ threshold: 10 })/ice.alignmentGuide.disable()。 - Undo / Redo(基于项目快照,最多 100 步)。
- 项目级保存 / 加载(
serializeProject()/loadProject())。
BPMN 2.0 记法(BpmnDesigner)
在同一套节点 / 连线 / 历史 / 快照机制上装载 BPMN 2.0 的业务记法,不另起一套模型:
- 八类图元:事件圆(开始 / 中间 / 结束 × 无 / 消息 / 定时 / 错误 / 终止触发)、网关菱形(排他 / 并行 / 包容 / 事件)、任务与子流程(用户 / 服务 / 脚本 / 发送 / 接收 / 手动角标)、数据对象、文本注释、池、泳道。
- 池 → 泳道 → 节点是真嵌套(引擎的容器能力),拖动池或泳道时内部图元与挂在它们上面的连线一起走;
池的标题带与泳道的标题带不参与内容区,不会被内部图元压住。
池标题横排在顶部 32px 名称带里;泳道标题按 BPMN 惯例逆时针旋转 90° 竖排(读向自下而上),
居中放在左侧 32px 名称带里;标题比泳道还长时按名称带长度截断加省略号(单行,完整标题仍在属性面板与
文档里)。用的是组件变换(
ICEText.transform.rotate = -90),不是引擎层竖排。 - 三种流:
sequence顺序流、message消息流(跨参与者,虚线 + 实心箭头)、association关联 (数据对象 / 注释);顺序流可带条件表达式与「默认流」斜杠标记,标记是派生装饰,放在工具层、不污染文档。 - BPMN 语义校验:每个池至少一个开始事件、顺序流不得跨池、消息流应连接不同参与者、网关分支是否齐全、 从开始事件的可达性等。
- BPMN 2.0 XML 互操作:
toBpmnXml()导出(含BPMNDI布局信息)、fromBpmnXml()导入; 这是保布局的交换格式,不是执行模型(条件只作为文本往返,无令牌仿真 / 边界事件订阅 / 多实例元数据)。
3. 界面预览
完整的 ER 模型(电商交易 + 用户权限):
实体字段与约束标记:
关系语义:自引用、多对多、一对一:
交互式编辑器(右侧面板可直接切换到「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. 快速开始
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 | 给排水符号图例:21 种符号一页看全(位号在上 / 名称在下 / 图形居中),可导出 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 | 独立的 React 集成示例工程(webpack + TypeScript),涵盖 ref / hook / onChange / 受控模式 |
python3 -m http.server 8899 # 然后访问 http://localhost:8899/examples/entity-editor.html5. 使用方式
EntityDesigner 是 Entity / Relation 之上的轻量应用层,负责把建模交互闭环串起来:
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的处理一致)。
也支持更底层的组件式用法:
const schema = IED.toSchemaObject(ice.childNodes);
const schemaText = IED.toSchemaString(ice.childNodes);导出的对象可直接构造 TypeORM 实体:
import { EntitySchema } from 'typeorm';
const schemas = designer.toSchemaObject().map((obj) => new EntitySchema(obj));5.2 流程图(FlowDesigner)
包内除 ER 之外还内置了一套流程图领域图元与应用层(同一个 ice 实例即可承载):
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() |
自定义形状(判定菱形 / 输入输出平行四边形)在 src/flow/flow_shapes.ts,走的是引擎的 ICEPath 子类机制。
流程图节点是复合组件(形状 + 标题由 kind/标题/配色派生):它们实现了引擎的 hasDerivedChildren(),
内部子组件不写进文档、载入时由构造函数按 state 重建——避免重复挂载,也让同一份数据的两次序列化结果保持一致。
可运行的完整示例见 examples/flowchart-editor.html;React 用法见 6.6;
AI Agent 生成流程图的 JSON DSL 见 ice-entity-designer-dsl。
5.3 BPMN 2.0(BpmnDesigner)
BpmnDesigner 继承 FlowDesigner,只补 BPMN 特有的事:顺序流上的条件 / 默认流标记(派生装饰,
放在工具层、不进文档)与语义校验。其余能力(建节点 / 连线、选择、增删改、撤销重做、快照、适应视图、订阅)全部沿用:
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,与画布逐像素同一口径
(绘制顺序、世界矩阵、样式合并、透明度、祖先裁剪、虚线、渐变、阴影、连线标签):
// 流程图 / 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 日期 × 持续天数 × 每日像素)、纵轴是行,任务条按天吸附拖动,
依赖线从「前置任务的结束」指向「后置任务的开始」。
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)
「流程怎么走」可以直接演示出来:令牌从开始事件出发,沿顺序流前进、在任务上停留、在排他网关选一条分支、 在并行网关一分为多,到达结束事件后消失。
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。
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)**的第二个完整示例:伪状态(初始 / 终止)、普通状态、
复合状态是容器(内部可放子状态,拖动父状态子状态跟着走),转移标签是 事件 [守卫] / 动作。
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。
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 功能编号对照、来源)。
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 胶水代码。
npm install ice-entity-designer ice-render react react-domimport { 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 <span>{designer ? `${designer.entities.length} 个实体` : '初始化中…'}</span>;
}
export default function App() {
const ref = useRef<EntityDesignerHandle>(null);
return (
<>
<button onClick={() => ref.current?.addEntity({ entityName: 'User' })}>新增实体</button>
<button onClick={() => console.log(ref.current?.toSchemaString())}>导出 Schema</button>
<EntityDesignerCanvas
ref={ref}
width={1200}
height={800}
defaultValue={initialProjectJson} // 可选:初始项目快照
onChange={({ snapshot, schema }) => save(snapshot)} // 模型变更
>
<Stats />
</EntityDesignerCanvas>
</>
);
}6.1 取用实例的两种方式
ref:命令式 API ——addEntity/connect/updateEntity/updateRelation/remove/loadProject/undo/redo/toSchemaObject/toSchemaString/validate/serializeProject。useEntityDesigner():在<EntityDesignerCanvas>子树内直接取到底层EntityDesigner实例(如上例的Stats)。
6.2 组件属性
| 属性 | 类型 | 说明 |
|---|---|---|
| value | string | 受控:项目快照,变化时同步进画布(内部变更经 onChange 上报,带循环保护) |
| defaultValue | string | 非受控:初始项目快照 |
| onChange | (payload: { snapshot, schema }) => void | 模型变更(增删改 / 载入 / undo / redo)后触发,snapshot 可直接用于自动保存 |
| onError | (payload: { phase, snapshot, error }) => void | 快照载入失败(非法 / 版本不兼容)时触发,默认 console.error;组件内部已捕获,不会把异常抛进渲染树 |
| onReady | (handle) => void | 实例就绪,回调里拿到命令式句柄 |
| width / height | number | 画布尺寸,默认 1200 × 800 |
| renderMode | 'dirty-rect' \| 'full' | 渲染模式,默认 dirty-rect |
| style / className | — | 作用于画布容器 |
| children | ReactNode | 渲染在上下文内,可直接 useEntityDesigner() |
6.3 受控用法
const [project, setProject] = useState(initialJson);
<EntityDesignerCanvas
value={project} // 外部改这个 → 同步进画布
onChange={({ snapshot }) => setProject(snapshot)} // 内部变更上报(同值回传不会重复载入,无回环)
width={1200}
height={800}
/>6.4 手动提供上下文
如果自己创建会话(例如画布在别处、只共享实例),用 EntityDesignerProvider 把实例交给任意子树:
import { createDesignerSession, EntityDesignerProvider } from 'ice-entity-designer/react';
const session = createDesignerSession(canvasEl);
// <EntityDesignerProvider designer={session.designer}><Toolbar /></EntityDesignerProvider>6.5 注意事项
- 生命周期 / StrictMode:
<EntityDesignerCanvas>挂载时创建 ICE + EntityDesigner,卸载时销毁。引擎侧init幂等、destroy会解绑全局监听与帧循环,因此 React StrictMode 双挂载安全。 - 仅客户端渲染:组件依赖 canvas,SSR(如 Next.js)请按客户端组件使用,例如
dynamic(() => import('./Designer'), { ssr: false })。 - React 是可选 peerDependency(
^18 || ^19),不使用 React 的项目不受影响。 - 子路径解析:现代解析器(webpack 5 / Vite / Node ESM)走
exports;老版本 TypeScript(< 4.7,或moduleResolution: "node")建议改用node16/bundler,包内另提供react.d.ts垫片以兼容旧解析器。
完整可运行示例(独立工程,webpack 构建):
ice-entity-designer-react-demo
6.6 流程图的 React 绑定
流程图有与 ER 完全同构的一套绑定:<FlowDesignerCanvas> + useFlowDesigner() + createFlowSession() + 命令式句柄:
import { useRef } from 'react';
import { FlowDesignerCanvas, useFlowDesigner } from 'ice-entity-designer/react';
import type { FlowDesignerHandle } from 'ice-entity-designer/react';
function Stats() {
const flow = useFlowDesigner();
return <span>{flow ? `${flow.nodes.length} 个节点 / ${flow.edges.length} 条连线` : '初始化中…'}</span>;
}
export default function FlowEditor() {
const ref = useRef<FlowDesignerHandle>(null);
return (
<>
<button onClick={() => ref.current?.addNode('decision', { title: '库存充足?' })}>加判定</button>
<button onClick={() => ref.current?.fitViewport()}>适应视图</button>
<FlowDesignerCanvas
ref={ref}
width={900}
height={700}
defaultValue={flowJson}
// 画布上拖动节点、改属性、载入、undo/redo 都会触发(拖拽是按帧合并的)
onChange={({ snapshot, counts }) => save(snapshot, counts)}
>
<Stats />
</FlowDesignerCanvas>
</>
);
}| 项 | 与 ER 的差异 |
|---|---|
| 命令式句柄 | addNode(kind, props) / connect({ sourceId, targetId, sourcePort, targetPort, label }) / updateNode / updateEdge / remove / load / undo / redo / serialize / toSnapshot / fitViewport |
| onChange 载荷 | { snapshot, counts: { nodes, edges } }(ER 是 { snapshot, schema }) |
| 初始快照键 | value / defaultValue 传流程图快照({ version, kind: 'flowchart', nodes, edges }),不是 ER 的项目快照 |
onChange 的语义与 ER 一致:任何改变模型的入口都会触发;额外多了一条——画布上拖动节点也会触发(FlowDesigner 订阅了引擎的 BEFORE_MOVE / AFTER_MOVE,并按帧合并),所以用 onChange 做自动保存能拿到拖拽后的最新坐标。
BPMN 目前走命令式 BpmnDesigner(见 5.3):它继承 FlowDesigner,
需要的容器嵌套 / 语义校验 / XML 互操作都在命令式实例上,暂未额外提供 React 组件;
React 里可沿用 createFlowSession 的模式自建一层封装。
7. 项目结构
7.1 一个「域包(domain pack)」由什么组成
域包 = 一个领域的记法 + 应用层 + 语义校验,跑在同一套引擎与同一套应用层机制上。现已落地 9 个域包:ER、流程图、BPMN 2.0、UML 类图、状态机、甘特、电力一次系统图、电力二次回路、给水排水工艺流程图 —— 边际成本 主要在「记法本身」,不在编辑器:
| 组成 | 复用什么 | 以 UML 为例 |
|---|---|---|
| 形状 | 引擎的复合组件(hasDerivedChildren):内部子组件按 state 派生、不进文档 | UmlClass:三段式类框;GanttTask:任务条 + 进度覆盖 |
| 连线 | 引擎折线(插槽吸附 / 正交·贝塞尔路由 / 标签 / 跟随宿主);端点标记进路径点集,因此描边、填充、导出都自动带上 | UmlRelation:六种关系 = 线型 + 三角/菱形/开放箭头 |
| 应用层 | FlowDesigner(选择 / 增删改 / 连线 / 撤销重做 / 快照 / 适应视图 / 订阅 / toSvg) | UmlDesigner 只重写「建什么图元 + 类型过滤」 |
| 语义校验 | 结构校验之外的部分自写,规则直白 | validateUml():重名类 / 悬空关系 / 继承成环 |
| 文档格式 | 引擎序列化(namespace:Type 的 typeId 注册表 + hasDerivedChildren),零登记 | 类与关系统统自动往返 |
| 互操作 | 有标准格式的域就做 | BPMN 2.0 XML(导入 + 导出,含 BPMNDI 布局);UML 类图与状态机的 PlantUML 文本互操作;甘特的 Mermaid gantt 文本互操作 |
| 交付物 | 示例页 + e2e + README + (可选)JSON DSL 与技能 | examples/uml-editor.html + e2e/uml-editor.spec.ts |
新开一个域包时,按这张表从上往下填即可;不要在域包里另造序列化、另造选择/历史、另造导出。
7.2 目录一览
src/
├── designer/EntityDesigner.ts # 应用层:选择 / 增删改 / 连接 / 校验 / 历史 / 项目存取 / 变更订阅
├── flow/ # 流程图(与 ER 并列的第二类领域图元)
│ ├── flow_shapes.ts # 自定义形状:判定菱形 / 输入输出平行四边形
│ ├── FlowNode.ts # 节点:四类预设(起止 / 处理 / 判定 / 输入输出)+ 居中标题
│ ├── FlowEdge.ts # 连线:正交 / 贝塞尔 + 箭头 + 分支标签,插槽吸附
│ └── FlowDesigner.ts # 应用层:建节点/连线、选择、增删改、历史、快照存取、适应视图
├── uml/ # UML 类图(domain pack:三段式类框 + 六种关系 + 语义校验)
│ ├── UmlClass.ts # 复合组件:类名 / 属性 / 方法三段,构造型,框高随成员增长
│ ├── UmlRelation.ts # 六种关系 = 线型 + 端点标记(三角/菱形/开放箭头,标记进路径点集)
│ └── UmlDesigner.ts # FlowDesigner 薄扩展:建 UML 图元 + 类型过滤 + validateUml()
├── gantt/ # 甘特图(第三个 domain pack:时间轴 + 按天吸附 + 依赖)
│ ├── gantt_date.ts # 日期工具(UTC 口径,YYYY-MM-DD ↔ 天数)
│ ├── GanttTask.ts # 任务条(复合组件)+ setPosition 按天吸附
│ ├── GanttRuler.ts # 图表框架:左列任务名 + 日期刻度 + 行线(派生重建)
│ ├── GanttDependency.ts # 依赖线(完成 → 开始)
│ └── GanttDesigner.ts # 时间轴换算 + syncChrome + validateGantt
├── bpmn/ # BPMN 2.0(FlowDesigner 之上的业务记法)
│ ├── BpmnSimulator.ts # 令牌仿真:沿顺序流推进、网关分叉、结束消亡(令牌在工具层)
│ ├── bpmn_shapes.ts # 形状:事件圆 / 网关菱形 / 任务角标 / 子流程标记 / 数据对象 / 注释 / 池泳道
│ ├── BpmnDesigner.ts # 应用层:容器真嵌套(池→泳道→节点)、条件与默认流标记、语义校验
│ ├── bpmn_validate.ts # BPMN 语义校验(开始事件 / 跨池顺序流 / 网关分支 / 可达性)
│ └── bpmn_xml.ts # BPMN 2.0 XML 导入导出(含 BPMNDI 布局)
├── statechart/ # 状态机(domain pack:伪状态 / 状态 / 复合状态容器)
│ ├── StateNode.ts # 状态节点:伪状态 / 普通状态 / 复合状态(容器,子状态随父平移)
│ ├── StateTransition.ts # 转移:标签写作 `事件 [守卫] / 动作`
│ ├── statechart_text.ts # PlantUML 状态图文本互操作(导入 + 导出)
│ └── StatechartDesigner.ts # 应用层:建图元 + 复合状态自动尺寸 + 语义校验
├── power/ # 电力一次系统图(单线图:符号库 + 拓扑 + 带电 + 校验)
│ ├── power_shapes.ts # 23 种一次设备符号(JB/T 5872-1991、GB/T 4728.1/3/4/6)
│ ├── power_voltage.ts # 电压等级色标(500kV / 220kV / 110kV / 35kV / 10kV / 6kV)
│ └── PowerDesigner.ts # 应用层:开关分合 / 拓扑求解 / 带电着色 / 母线 T 接 / 语义校验
├── secondary/ # 电力二次回路(保护电流回路 + 端子排)
│ ├── secondary_shapes.ts # 10 种二次元件符号(GB/T 4728.7,文字符号用 C37.2 功能编号)
│ └── SecondaryDesigner.ts # 应用层:回路编号 / 端子排容器 / 二次语义校验
├── water/ # 给水排水工艺流程图(水厂 / 污水厂:AAO 主线 + 污泥线)
│ ├── water_shapes.ts # 21 种符号(GB/T 50106 图例;水线蓝 / 污泥线黄)
│ └── WaterProcessDesigner.ts # 应用层:介质 + 管径、工艺校验、流径分析(关阀断流)
├── er-component/
│ ├── Entity.ts # 实体:表头 + 字段列表 + 约束标记 + TypeORM 序列化
│ └── Relation.ts # 关系:基数 / 箭头 / 标签语义 / 连接槽位
├── react/
│ ├── EntityDesignerCanvas.ts # React 组件:画布 + 生命周期 + 命令式句柄
│ ├── session.ts # 会话封装:创建 / 销毁 ICE + EntityDesigner
│ ├── context.ts # 上下文与 useEntityDesigner()
│ └── index.ts # 子路径导出 ice-entity-designer/react
├── utils/
│ ├── serialization_util.ts # 画布 → TypeORM Schema
│ ├── schema_validator.ts # 轻量 Schema 校验
│ ├── camelcase_util.ts # 命名转换
│ └── pluralize_util.ts # 复数化(多对多属性名)
└── index.ts # 对外导出(核心,不含 React)8. 开发与测试
| 命令 | 说明 |
|---|---|
| npm start | 监听模式构建(开发) |
| npm run build | 清理并完整构建(类型声明 + JS 产物) |
| npm run types:check | 仅做 TypeScript 类型检查 |
| npm test | 运行单元测试(Jest) |
| npm run test:e2e | 浏览器端到端回归(Playwright,先 npm run build;覆盖 10 个示例页:ER / 流程图 / BPMN / UML / 状态机 / 甘特 / 电力一次 / 电力符号表 / 电力二次 / 给水排水,各自做交互断言与 console 零报错检查) |
| npm run pretty | Prettier 格式化源码 |
9. 环境要求与依赖
- Node.js >= 18,npm >= 9。
ice-render是 peer 依赖(^1.4.10):运行时和类型都使用宿主提供的那一份引擎, 与ice-web-components/ice-chart等上层包保持同一份内核。- 运行时:
import { ICE, EntityDesigner } from 'ice-entity-designer',其中ICE由 peer 的ice-renderre-export。 - 类型:本包
.d.ts保持对ice-render的模块引用,不再 vendor 一份类型。
- 运行时:
- React 绑定为可选对等依赖
react/react-dom(^18 || ^19),仅在使用ice-entity-designer/react时需要。 - 构建链:Rollup 3 + Babel 7 + TypeScript 5.9;测试框架:Jest 29。
