鸿蒙原生流程图 & 审批流组件 hmflowkit

鸿蒙原生流程图 & 审批流组件 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 支持 LINEQUAD 两种段类型,后者可表达贝塞尔曲线边缘(如 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.ArkTSXmlPullParser(parseXml 回调 API),支持:

  • 15+ 种节点类型映射(事件、任务、网关、子流程、调用活动)
  • Collaboration/LaneSet 泳池泳道解析(嵌套 Lane + flowNodeRef 关联)
  • BPMNShape 坐标与 BPMNEdge waypoints 提取
  • 命名空间前缀自动剥离
  • 字符引用预处理(&&amp; 等)
  • 三种解析模式:严格 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 的套壳。

相关推荐
网易云信18 小时前
全框架覆盖!网易智企IM鸿蒙生态适配再进一步
人工智能·aigc·harmonyos
TrisighT1 天前
我用 AI 逆向了 ArkTS @Builder 的编译产物,看完再也不敢乱写嵌套了
ai编程·harmonyos·arkts
ONEDAY2 天前
HarmonyOS 深色模式适配实践:从资源、WebView 到网络图统一处理
harmonyos
鸿蒙开发3 天前
鸿蒙(HarmonyOS NEXT)表单校验别再手撸正则了 —— 我写了个 ArkTS 版 zod
harmonyos
TrisighT3 天前
ArkTS 的 @BuilderParam 你八成只用了皮毛——那个尾随闭包写法差点被我当 bug 删了
harmonyos·arkts·arkui
ONEDAY4 天前
HarmonyOS 多 Product 构建实践:一套代码生成多个产物
harmonyos
TT_Close4 天前
别劝退了!5秒搞定 Flutter 鸿蒙 FVM 起跑线
flutter·harmonyos·visual studio code
TrisighT4 天前
ArkTS 列表滚动时为什么会闪现旧数据?我扒了 LazyForEach 的复用逻辑
harmonyos·arkts·arkui
MonkeyKing4 天前
鸿蒙ArkTS深度剖析:ArkTS与TS/JS核心差异、静态强类型实战优势
typescript·harmonyos