鸿蒙原生流程图 & 审批流组件 hmflowkit ------ 三行代码,让 BPMN/drawio 在 ArkUI 上跑起来
hmflowkit --- 纯 ArkTS + Canvas 2D 实现,零 WebView、零第三方依赖。支持 BPMN 2.0 XML 与 drawio mxGraph 双格式,内置审批流状态可视化。
演示
| BPMN Kitchen Sink | drawio 跨职能流程图 |
|---|---|
![]() |
![]() |
| 审批流状态可视化 | 泳池泳道 + 多平面钻取 |
|---|---|
![]() |
![]() |
一、为什么造这个轮子?
鸿蒙 PC 正在进入政企市场,OA、政务、金融类应用对流程图展示 与审批流可视化的需求是刚需。然而现状是:
arduino
某市财政局 OA 系统,鸿蒙 PC 客户端
"领导,我想看看这个采购审批现在到哪个环节了。"
【点击审批流】
┌──────────────────────────────────────┐
│ Loading... WebView 套 bpmn.js │
│ 白屏 3 秒 → 渲染 2 秒 → 字体错位 │
│ 双指捏合 → 没反应(WebView 手势冲突) │
│ 快捷键 → 全被 WebView 吃掉 │
└──────────────────────────────────────┘
用户:这和浏览器有区别吗?
开发:......没有。
WebView 方案的痛点:
- ❌ 启动慢,内存高(一个流程图加载完整浏览器内核)
- ❌ 手势冲突(双指缩放、拖拽平移被 WebView 吃掉)
- ❌ 与 ArkUI 组件体系割裂,无法跟随系统明暗主题
- ❌ 后端 Flowable/Activiti 产出的 BPMN XML 在前端缺少原生渲染方案
- ❌ drawio 文件(mxGraph XML)在鸿蒙上完全无法直接展示
这就是 hmflowkit 存在的理由。 一个纯 ArkTS 实现、零依赖、双格式(BPMN + drawio)的鸿蒙原生流程图引擎。
二、三行代码接入
安装
bash
ohpm install hmflowkit
场景一:渲染 BPMN 流程图
typescript
import { FlowViewer } from 'hmflowkit';
// 一行渲染 BPMN XML
FlowViewer({ xml: this.bpmnXmlString })
场景二:渲染 drawio 流程图
typescript
FlowViewer({ drawioXml: this.drawioXmlStr })
场景三:审批流状态可视化
typescript
FlowViewer({
xml: this.bpmnXml, // BPMN 2.0 XML 字符串
nodeStatuses: this.statusMap, // Map<nodeId, NodeStatus> 审批状态
edgeTrails: this.trails, // EdgeTrail[] 流转路径
approvalConfig: this.config // ApprovalOverlayConfig 视觉参数
})
三行代码,一个完整的 BPMN/drawio 审批流渲染在你的鸿蒙应用中。内置拖拽平移、双指缩放、自动适配视口、明暗主题跟随------无需额外配置。
运行效果(GIF 演示)
上图四个 GIF 分别展示了项目的全部核心能力:
- BPMN Kitchen Sink:完整的 BPMN 2.0 元素渲染覆盖(事件、网关、任务、子流程、泳道)
- drawio 跨职能流程图:mxGraph XML 解析 + BPMN 2.0 形状全映射 + 泳道嵌套
- 审批流状态可视化:节点状态内边框 + 角标 + 流转路径四态染色 + 脉冲动画 + 浮动信息面板
- 多平面钻取导航:子流程展开/折叠 + 面包屑导航 + 泳池泳道完整渲染
三、技术亮点 & 核心设计
3.1 四层架构:数据驱动,职责分明
┌─────────────────────────────────────────────────┐
│ UI 组件层 FlowViewer / ApprovalInfoPanel │
├─────────────────────────────────────────────────┤
│ Renderer 层 NodeRenderer / EdgeRenderer │
│ PoolLaneRenderer / PathRenderer │
├─────────────────────────────────────────────────┤
│ Model 层 GraphModel(唯一数据源,不可变) │
│ PlaneHierarchy(多平面层级) │
├─────────────────────────────────────────────────┤
│ Parser 层 BpmnXmlParser / DrawioXmlParser │
└─────────────────────────────────────────────────┘
核心约束:
- Model 层是唯一数据源。GraphModel 采用不可变数据模式,所有写操作返回新实例。Renderer 只读不写,Interaction 通过 Model 的方法修改数据。
- 不可跨层调用。UI 组件层不直接操作 Canvas,Renderer 不直接处理手势。
- 接口优先于实现。每个模块先定义 interface,再写实现类。
3.2 GraphModel:不可变数据模型
整个渲染引擎的数据中枢,支持节点、边、泳池/泳道的完整 CRUD + 级联删除 + 序列化:
typescript
// 构建模型
let model = GraphModel.createEmpty()
.addNode(new GraphNode('start', NodeType.START_EVENT, 100, 200, 36, 36, '开始'))
.addNode(new GraphNode('task1', NodeType.USER_TASK, 200, 180, 120, 60, '提交申请'))
.addEdge(new GraphEdge('e1', 'start', 'task1', [], new EdgeStyle(), ''))
// 不可变------每次写操作返回新实例,原始实例不变
let model2 = model.moveNode('task1', 300, 180);
// 序列化
let snapshot = model.toJSON();
let restored = GraphModel.fromJSON(snapshot);
为什么选择不可变? React/Redux 生态已经证明不可变数据在 UI 渲染场景的优势:状态变更可追溯、渲染层可做引用对比跳过无变化重绘、调试时可回溯任意历史状态。
3.3 数据驱动形状几何系统(ShapeDefinition + PerimeterRouter + PathRenderer)
这是整个渲染引擎最底层的几何抽象。四个核心概念:
scss
┌──────────────────┐
│ ShapeDefinition │ ← 声明形状的几何定义
│ - perimeterPoints[] │ 轮廓点序列
│ - renderKind │ 渲染方式
│ - iconPlacements[] │ 图标锚点
└────────┬─────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌──────▼──────┐ ┌───▼────┐ ┌─────▼──────┐
│ PathRenderer│ │Perimeter│ │NodeRenderer │
│ 轮廓绘制 │ │ Router │ │ 注册表模式 │
│ - ellipse │ │交点计算 │ │ register() │
│ - rhombus │ │椭圆/菱形│ │ 用户可扩展 │
│ - polyline │ │/矩形 │ │ │
└──────────────┘ └────────┘ └─────────────┘
PerimeterRouter --- 精确的椭圆/菱形/矩形 perimeter 交点计算,使用连续数学公式而非离散采样。当连线需要从形状边界精确出发时,调用 computeIntersection(shape, angle) 返回精确交点坐标。
ShapeDefinition --- 每个形状类型有唯一的 perimeter 路径定义。目前注册了 22 种形状(BPMN 事件、网关、任务、drawio 形状等)。PerimeterPoint 支持 LINE 和 QUAD 两种段类型,后者可表达贝塞尔曲线边缘(如 drawio document 形状的波浪底边)。
PathRenderer --- 根据 renderKind 分发绘制策略:
| renderKind | 实现 | 适用形状 |
|---|---|---|
NATIVE_ELLIPSE |
ctx.ellipse() |
事件圆圈 |
NATIVE_RHOMBUS |
四边中点分段 lineTo | 网关菱形 |
POLYLINE |
arcTo 圆角矩形 |
任务矩形、泳池容器 |
NodeRenderer 注册表模式 --- 每个形状对应一个 INodeDrawer 实现,通过 NodeRenderer.register(type, drawer) 注册:
typescript
interface INodeDrawer {
draw(ctx: CanvasRenderingContext2D, node: GraphNode, config: RenderConfig,
offsetX: number, offsetY: number, zoom: number): void;
}
// 用户可注册自定义形状
NodeRenderer.register('myCustomShape', new MyCustomDrawer());
这是跨格式架构 的关键------BPMN 的 startEvent、drawio 的 ellipse;shape=doubleEllipse,都映射到同一个 ShapeDefinition,走同一套渲染管线。
3.4 双格式解析:BPMN 2.0 XML + drawio mxGraph
BPMN 解析(BpmnXmlParser) --- 基于 @kit.ArkTS 的 XmlPullParser(parseXml 回调 API),支持:
- 15+ 种节点类型映射(事件、任务、网关、子流程、调用活动)
- Collaboration/LaneSet 泳池泳道解析(嵌套 Lane + flowNodeRef 关联)
- BPMNShape 坐标与 BPMNEdge waypoints 提取
- 命名空间前缀自动剥离
- 字符引用预处理(
&→&等) - 三种解析模式:严格
parse()、宽松parseBestEffort()(部分失败仍返回)、多平面parseHierarchy()
⚠️ 关键踩坑记录: parseXml 回调中 tokenValueCallback(START_TAG) 在 attributeValueCallback 之前 触发(与官方文档暗示顺序相反),所以所有业务逻辑必须在 END_TAG 中处理。此外 ignoreNameSpace: true 不会剥离 getName() 前缀,需手动 localName()。
drawio 解析(DrawioXmlParser + DrawioStyleParser) --- mxGraph XML → GraphModel,包含:
- HTML 实体净化(
mxGraph源码嵌入 HTML 标签) - 两趟构建(先建 node/shape 索引,再连边)
- style 字符串解析(BPMN 2.0 形状全映射:8 种 Event outline → startEvent/endEvent/intermediateEvent/boundaryEvent、4 种 Gateway、8 种 Task marker、SubProcess/CallActivity、DataObject/DataStore、Annotation、Swimlane/Pool)
- 自动 perimeter routing(drawio
<Array as="points">只是中间路由点,不连形状边界------parser 自动补 source/target perimeter) - 跨职能流程图支持(table > tableRow > swimlane 嵌套层级坐标累加)
跨格式配置层(ShapeConfig) --- BPMN 和 drawio 的解析结果通过统一的 ShapeConfig 注册表映射到相同的渲染管线:
css
BPMN XML ──→ BpmnXmlParser ──→ GraphModel ──┐
├──→ FlowViewer ──→ Canvas
drawio XML ──→ DrawioXmlParser ──→ GraphModel ──┘
这意味着后续添加 Visio、UML 等格式时,只需新增 Parser + 注册映射,渲染层完全不变。
3.5 多平面钻取导航(PlaneHierarchy)
BPMN 2.0 中一个 XML 文件可包含多个 <BPMNDiagram>,每个对应一个子流程平面。hmflowkit 通过 PlaneHierarchy 实现完整的钻取导航:
typescript
// 自动检测多平面,无需手动指定
let hierarchy = BpmnXmlParser.parseHierarchy(xml);
FlowViewer({
model: hierarchy.getRootModel(),
planeHierarchy: hierarchy
})
核心机制:
- Post-scan 后处理:绕过 parseXml 回调时序陷阱,在解析完成后通过字符串扫描建立 diagram→plane 映射 + shape/edge 路由
- expanded / collapsed 双渲染:展开态子流程在 layer 0 渲染为背景容器(居中顶部标签),折叠态在 layer 4 渲染为可交互节点(蓝色 + 标记)
- 面包屑导航 :
Root > 采购子流程 > 审批子流程,点击任意段跳转 - HitTest 最小面积优先:嵌套元素内部小元素优先于父容器大包围盒
- 单平面 XML 完全向后兼容 :
parseHierarchy()检测到单平面时自动回退parse()
3.6 审批流状态可视化
专为 OA/政务审批流场景设计,以最小接入成本实现---状态可视化,不内置审批逻辑:
typescript
// 数据接入------适配器模式,对接任意审批引擎
interface IApprovalAdapter {
getNodeStatus(nodeId: string): NodeStatus | null;
getEdgeTrails(): EdgeTrail[];
}
// 内置参考实现
let adapter = new FlowableHistoryAdapter(historyEntries);
四层视觉编码:
| 层次 | 视觉元素 | 编码内容 |
|---|---|---|
| 内边框 | 形状自适应(椭圆/矩形),borderInset 可配 |
6 种审批状态(待审/已批/驳回/当前/跳过/转办) |
| 角标 | topRight / topLeft / bottomRight,圆形带字 |
审批人头像/姓名缩写 |
| 流转路径 | 四态边染色:active=绿色加粗、rejected=红色虚线+标签、historical=浅灰细线、skipped=灰色短虚线 | 审批流转轨迹 |
| 脉冲动画 | setTimeout 递归驱动,5 周期自停,alpha 0.3↔1.0 振荡 |
当前审批节点高亮 |
信息面板:200×300 浮动气泡,点击节点弹出------显示审批人、时间、意见、会签子实例列表。📋 一键复制全文到 pasteboard。暗色主题自动跟随。
三套配色预设 :CLASSIC(琥珀+蓝+红)、GOVERNMENT(红蓝灰政务风格)、DARK(暗色适配)。传入自定义 ApprovalOverlayConfig 可完全替换视觉参数。
3.7 泳池/泳道(Pool/Lane)完整支持
typescript
// 数据模型
let pool = new Pool('pool1', '采购部门', true, bounds, [lane1, lane2]);
model = model.addPool(pool);
// 查询节点所属泳道
let lane = model.getLaneByNode('task_apply');
关键实现:
- 嵌套 Lane :
<laneSet>内可嵌套<childLaneSet>,递归解析 - 双朝向标题栏 :水平泳池(
isHorizontal=true)标题在左侧,垂直在顶部;标题文字用ctx.rotate(-PI/2)横排 - 多 Pool 共享 Process:每个 Pool 复制全部 Lane,渲染时通过边界检查过滤不重叠的 Lane
- Lane 边界过滤:只渲染在 Pool 范围内的 Lane 节点
- 自动适配 :
fitToView考虑泳池边界,标题栏 Z-order 正确
3.8 CanvasManager:手势交互 & 视口管理
typescript
let cm = new CanvasManager(0.1, 5.0, 1.0); // minZoom, maxZoom, initialZoom
// 手势驱动
cm.pan(dx, dy); // 拖拽平移
cm.zoomAt(cx, cy, delta); // 以 (cx,cy) 为中心缩放(双指 pinch)
// 坐标转换
let cp = cm.screenToCanvas(sx, sy); // 屏幕 → 画布
let sp = cm.canvasToScreen(cx, cy); // 画布 → 屏幕
// 自动适配
cm.fitToView(contentW, contentH, canvasW, canvasH, padding);
关键细节:
- 四向旋转(0°/90°/180°/270°):offset 位于旋转变换内侧,所有屏幕↔offset 转换须通过逆旋转矩阵 R⁻¹
- 点阵性能保护 :可见点数 >
MAX_VISIBLE_DOTS(5000) 时跳过点阵网格渲染,防止中等缩放级别拖动时帧率崩溃 - 16ms 节流 :
renderFrame()通过 requestAnimationFrame 控制重绘频率
3.9 明暗主题自动适配
默认配色自动跟随系统明暗模式,无需额外配置:
typescript
// EntryAbility
onCreate(want, launchParam) {
AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode);
}
onConfigurationUpdate(newConfig) {
AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
}
传入自定义 RenderConfig 可覆盖默认配色,此时不受系统主题切换影响:
typescript
let config = new RenderConfig();
config.taskFillColor = '#F5F5F5';
config.edgeStrokeColor = '#2196F3';
// ... 30+ 个可配置字段
FlowViewer({ xml: this.bpmnXml, renderConfig: config })
3.10 Debug 侧边栏 & 可观测性
开发/调试阶段可开启 300px 左侧调试面板(7 区段可折叠):
yaml
┌─ Statistics ─────────────────┐
│ Nodes: 47 Edges: 52 │
│ Pools: 3 Lanes: 12 │
├─ Timeline ───────────────────┤
│ [0ms] BPMN parse start │
│ [23ms] XML token stream done│
│ [47ms] GraphModel built │
├─ Performance ────────────────┤
│ FPS: 58 Frame: 16ms │
│ HitTest: 0.3ms │
├─ HitTest ────────────────────┤
│ Last: nodeId=task_apply │
│ Type: NODE Layer: 4 │
└──────────────────────────────┘
DebugCollector --- 286 行数据收集器,13 个查询方法 + 环形计时缓冲。ParseMeta 在解析期间自动收集元数据。
3.11 渲染管线:Z-order & 可扩展性
所有绘制单元按 layerPriorities 配置的 Z-order 渲染:
typescript
// RenderConfig 默认层序
layerPriorities: {
'poolLane': 0, // 最底层:泳道背景
'subProcess': 1, // 子流程容器
'edge': 3, // 连线(在节点下方)
'task': 4, 'gateway': 4, // 节点主体层
'startEvent': 5, 'endEvent': 5, // 事件(顶层覆盖连线)
'boundaryEvent': 6 // 边界事件(最顶层)
}
连线在节点下方但事件在上方------这保证了视觉正确的遮挡关系。
3.12 自动化测试 & 工程质量
| 指标 | 数据 |
|---|---|
| 测试框架 | @ohos/hypium (describe/it/expect) |
| 测试数量 | 262 项自动化单元测试 |
| 覆盖模块 | GraphModel(44) + BpmnXmlParser(42) + CanvasManager(17) + HitTestManager(13) + RenderConfig(5) + DrawioStyleParser(47) + DrawioXmlParser(19) + ShapeDefinition(9) + PerimeterRouter(10) + DebugCollector(13) + ApprovalTypes(18) + StatusOverlayRenderer(4) + FlowableHistoryAdapter(6) |
| 运行方式 | sh test_all.sh 编译验证 / DevEco Studio → ohosTest → Run |
| 测试原则 | 纯数据/算法测试,不依赖 Canvas mock 或 UI 组件 |
| 平台兼容 | HarmonyOS 5.0+ / OpenHarmony 5.0+,移动端 + PC 端通用 |
四、快速开始(完整示例)
typescript
import { FlowViewer, BpmnXmlParser, NodeStatus, EdgeTrail } from 'hmflowkit';
@Entry
@Component
struct ApprovalPage {
@State bpmnXml: string = '';
@State statuses: Map<string, NodeStatus> = new Map();
@State trails: EdgeTrail[] = [];
aboutToAppear() {
// 1. 从后端加载 BPMN XML
this.bpmnXml = /* fetch from Flowable/Activiti API */;
// 2. 注入审批历史数据
this.statuses.set('task_apply', new NodeStatus('task_apply', 'approved', '张三'));
this.statuses.set('task_manager', new NodeStatus('task_manager', 'approved', '李四'));
this.statuses.set('task_director', new NodeStatus('task_director', 'current', '王五'));
// 3. 标记流转路径
this.trails = [
new EdgeTrail('flow_apply_to_manager', 'active', '', Date.now(), '系统'),
new EdgeTrail('flow_manager_to_director', 'active', '', Date.now(), '系统'),
];
}
build() {
FlowViewer({
xml: this.bpmnXml,
nodeStatuses: this.statuses,
edgeTrails: this.trails,
onNodeClick: (nodeId) => {
// 点击节点 → 显示审批详情
}
})
}
}
五、参考项目 & 架构借鉴
hmflowkit 的设计从以下成熟项目中汲取了灵感,但不引入任何依赖:
| 项目 | 借鉴内容 |
|---|---|
| bpmn.js / diagram-js | BPMN 2.0 XML 解析、diagram-js 与 bpmn-js 分离模式 |
| LogicFlow | GraphModel 不可变数据设计、插件扩展机制 |
| AntV X6 | Canvas 虚拟 DOM 思路、Hit Testing 策略 |
| ofdkit-harmony | ArkUI Canvas 渲染模式、分层架构、ohpm 发布方式 |
六、项目信息 & 欢迎共建
| 项目 | 链接 |
|---|---|
| GitHub | github.com/liz7up/hm_f... |
| ohpm 三方库 | ohpm.openharmony.cn/#/cn/detail... |
| 许可证 | Apache-2.0 |
正在寻找
- 第一个真实用户 --- 如果你在做鸿蒙 OA/政务/金融项目,需要流程图/审批流可视化,我可以帮你免费接入和定制
- 技术反馈 --- API 设计、架构、文档或任何让你觉得别扭的地方,直接提 Issue
- 代码贡献 --- BPMN 元素覆盖率提升、新格式解析器(Visio/UML)、性能优化,欢迎 PR
这个项目诞生于一个简单的想法:鸿蒙生态需要原生基础设施------而不是 WebView 的套壳。



