npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blueking/flow-canvas

v0.0.13

Published

通用流程画布编辑引擎

Downloads

705

Readme

@blueking/flow-canvas 使用文档

概述

@blueking/flow-canvas 是一个通用流程画布编辑引擎,基于 AntV X6 构建,提供数据驱动的图编辑能力。适用于 DAG / 流程图 / 工作流等场景。

补充文档:

安装

npm install @blueking/flow-canvas

项目中另外还需要安装以下前置依赖

npm install @antv/x6 @antv/x6-plugin-minimap @antv/x6-plugin-selection @antv/x6-plugin-snapline @antv/x6-plugin-dnd @antv/x6-vue-shape modern-screenshot vue

快速开始

零配置启动

使用 createDefaultSchema 可零配置启动一个可拖拽、可连线的画布:

<template>
  <CanvasLayout :editor="editor" :palette-items="paletteItems">
    <CanvasRuntime :editor="editor" @ui-event="handleUiEvent" />
    <CanvasToolbar :editor="editor" />
  </CanvasLayout>
</template>

<script setup lang="ts">
  import {
    useCanvasEditor,
    CanvasRuntime,
    CanvasLayout,
    CanvasToolbar,
    createDefaultSchema,
    createEmptyFlowModel,
    searchPlugin,
    selectionPlugin,
    snaplinePlugin,
    type CanvasUiEvent,
  } from '@blueking/flow-canvas';
  import '@blueking/flow-canvas/style';

  const { schema, paletteItems } = createDefaultSchema();

  const editor = useCanvasEditor({
    initialFlowModel: createEmptyFlowModel(),
    schema,
    plugins: [selectionPlugin(), snaplinePlugin(), searchPlugin()],
  });

  function handleUiEvent(event: CanvasUiEvent) {
    console.log('UI 事件:', event.type);
  }
</script>

传入 editor prop 后,CanvasLayout 会自动在侧边栏渲染内置节点面板 CanvasNodePalette,用户可直接拖拽节点到画布。

自定义 Schema

如需完全控制节点/边类型,可手动定义 CanvasSchema

<template>
  <CanvasLayout>
    <template #sidebar>
      <MySidebar :editor="editor" />
    </template>
    <CanvasRuntime :editor="editor" @ui-event="handleUiEvent" />
    <CanvasToolbar :editor="editor" />
  </CanvasLayout>
</template>

<script setup lang="ts">
  import {
    useCanvasEditor,
    CanvasRuntime,
    CanvasLayout,
    CanvasToolbar,
    createBuiltinEdgeTypes,
    selectionPlugin,
    snaplinePlugin,
    connectionValidatorPlugin,
    type CanvasSchema,
    type CanvasUiEvent,
    type FlowModel,
  } from '@blueking/flow-canvas';
  import '@blueking/flow-canvas/style';
  import MyTaskNode from './components/my-task-node.vue';

  const builtinEdgeTypes = createBuiltinEdgeTypes();

  const schema: CanvasSchema = {
    nodeTypes: {
      task: {
        component: MyTaskNode,
        getSize: () => ({ width: 180, height: 60 }),
      },
    },
    edgeTypes: {
      bezier: builtinEdgeTypes.bezier,
    },
    defaultEdgeType: 'bezier',
  };

  const initialFlowModel: FlowModel = {
    version: '1.0',
    nodes: {
      node1: { id: 'node1', type: 'task', position: { x: 100, y: 200 }, label: '任务 1' },
      node2: { id: 'node2', type: 'task', position: { x: 400, y: 200 }, label: '任务 2' },
    },
    edges: {
      edge1: {
        id: 'edge1',
        source: { nodeId: 'node1', portId: 'right' },
        target: { nodeId: 'node2', portId: 'left' },
      },
    },
  };

  const editor = useCanvasEditor({
    initialFlowModel,
    schema,
    plugins: [
      selectionPlugin(),
      snaplinePlugin(),
      connectionValidatorPlugin((ctx) => {
        if (ctx.sourceNode.id === ctx.targetNode.id) {
          return { valid: false, reason: '不允许自环连线' };
        }
        return { valid: true };
      }),
    ],
    onFlowModelChange(event) {
      console.log('流程变更:', event.flowModel);
    },
  });

  function handleUiEvent(event: CanvasUiEvent) {
    if (event.type === 'node.click') {
      console.log('点击了节点:', event.nodeId);
    }
  }
</script>

这个示例刻意省略了 getPorts,因为当前版本在未提供 getPorts / node.ports 时会自动补齐默认四向端口,普通自定义节点通常只需要关注 componentgetSize

核心 API

useCanvasEditor(options)

创建画布编辑器实例。

CanvasEditorOptions

| 参数 | 类型 | 必填 | 默认 | 说明 | | ----------------- | ---------------------- | ---- | -------- | --------------------------------------- | | initialFlowModel | FlowModel | 是 | - | 初始流程模型 | | schema | CanvasSchema | 是 | - | 节点/边类型定义 | | plugins | CanvasPlugin[] | 否 | [] | 插件列表 | | mode | CanvasMode | 否 | 'edit' | 'edit' / 'readonly' / 'thumbnail' | | historyOptions | CanvasHistoryOptions | 否 | - | 历史配置 | | idGenerator | IdGenerator | 否 | 内置生成 | 自定义节点/边 ID 生成函数 | | onCommandResult | (result) => void | 否 | - | 命令执行结果回调 | | onFlowModelChange | (event) => void | 否 | - | FlowModel 变更回调 |

CanvasEditorContext(返回值)

| 属性/方法 | 类型 | 说明 | | ------------------------- | --------------------------------------------- | ------------------------------ | | flowModel | Readonly<Ref<FlowModel>> | 当前流程模型(响应式) | | history | CanvasHistory | 历史管理器 | | schema | CanvasSchema | Schema 定义 | | mode | Ref<CanvasMode> | 当前模式 | | selectionMode | Ref<boolean> | 是否启用框选模式 | | api | Ref<CanvasApi \| null> | 画布 API(Runtime 挂载后可用) | | toolbarItems | Ref<CanvasToolbarItem[]> | 插件聚合的工具栏项 | | idGenerator | IdGenerator | 节点/边 ID 生成函数 | | extendedApi | Record<string, Function> | 插件扩展的 API | | executeCommand(envelope) | (CommandEnvelope) => CommandExecutionResult | 执行命令 | | replaceFlowModel(model) | (FlowModel) => void | 整体替换流程模型 | | setMode(mode) | (CanvasMode) => void | 切换模式 | | setSelectionMode(enabled) | (boolean) => void | 切换框选模式 |

FlowModel 数据结构

interface FlowModel {
  version?: '1.0'; // 数据模型版本标识,可选,未传时引擎自动补全为 '1.0'
  nodes: Record<string, FlowNodeModel>; // 节点字典,key = nodeId
  edges: Record<string, FlowEdgeModel>; // 边字典,key = edgeId
  meta?: Record<string, unknown>; // 全局元数据
  extensions?: Record<string, unknown>; // 扩展数据
}

interface FlowNodeModel {
  id: string;
  type: string; // 对应 schema.nodeTypes 的 key
  position: { x: number; y: number };
  label?: string;
  ports?: FlowPortModel[]; // 业务自定义端口;未提供 getPorts() 时优先读取,缺省时引擎自动补四向默认端口
  payload?: Record<string, unknown>; // 业务数据
  extensions?: Record<string, unknown>; // 扩展数据
}

interface FlowEdgeModel {
  id: string;
  type?: string; // 对应 schema.edgeTypes 的 key
  source: { nodeId: string; portId?: string };
  target: { nodeId: string; portId?: string };
  labels?: FlowEdgeLabelModel[];
  payload?: Record<string, unknown>;
  extensions?: Record<string, unknown>;
}

CanvasSchema 配置

节点类型定义

interface CanvasNodeDefinition {
  component: Component; // Vue 组件,渲染节点内容
  getSize: (node) => { width; height };
  getPorts?: (node) => FlowPortModel[];
  getBehavior?: (node, ctx) => NodeBehaviorConfig;
  x6CellConfig?: Record<string, unknown>; // 透传给 X6 的额外配置
}

节点 Vue 组件内可通过 inject('getNode') 获取 X6 Node 实例,读取 node.getData() 访问 FlowNodeModel

端口来源与 getPorts 选型

getSize 是必填;getPortsgetBehavior 都是可选。

运行时解析节点端口的优先级如下:

  1. definition.getPorts(node)
  2. node.ports
  3. 默认四向端口(top/right/bottom/left)

这意味着:

  • 不写 getPortsnode.ports 也未提供时,引擎会自动补一套默认四向端口,位置位于四条边中点
  • 默认 quick-add 会直接绑定到这套默认端口中的 group: 'right',因此普通自定义节点只定义 component/getSize/getBehavior 也能保持默认右侧交互
  • 引擎内置的是四个方向端口组的默认位置和样式模板;默认兜底端口也复用这套约定
  • 如果你想显式禁用端口,需要返回空数组,例如 getPorts: () => [] 或设置 node.ports = []

推荐使用方式:

  • 端口结构按节点类型固定时,用 getPorts
  • 端口结构需要根据当前节点数据动态计算时,用 getPorts
  • 端口本身属于业务数据、需要随 FlowModel 持久化或导入导出时,用 node.ports
  • 如果同时提供了 getPortsnode.ports,运行时以前者为准

边类型定义

interface CanvasEdgeDefinition {
  router?: string | { name: string; args?: Record<string, unknown> };
  connector?: string | { name: string; args?: Record<string, unknown> };
  style?: (edge, state: EdgeRenderState) => EdgeStyle; // 动态样式
  labelDraggable?: boolean;
  x6EdgeConfig?: Record<string, unknown>; // 透传给 X6 的额外配置

  // @experimental — 尚未完整实现,请勿在生产环境依赖
  // labelRenderer?: Component;
}

EdgeRenderState 包含 selectedhighlightedhovered 三个布尔值,style() 可据此返回不同的 strokestrokeWidthstrokeDasharray

注意: labelRenderer 字段在类型定义中存在但标记为 @experimental,当前版本尚未完整实现。如需自定义边标签样式,建议通过 x6EdgeConfig.defaultLabel 配置 X6 原生标签 markup。

createDefaultSchema — 开箱即用的 Schema 工厂

createDefaultSchema 提供内置的节点类型和边类型定义,可零配置快速启动画布。

import { createDefaultSchema } from '@blueking/flow-canvas';

const { schema, paletteItems } = createDefaultSchema(options?);

DefaultSchemaOptions

| 参数 | 类型 | 默认 | 说明 | | --------------- | --------------------------------------- | ------------- | ---------------------------------------------- | | nodeTypes | Record<string, DefaultNodeTypeConfig> | 7 种内置类型 | 自定义节点类型列表,传入后替换内置类型 | | defaultEdgeType | string | 'manhattan' | 默认连线类型,内置 'manhattan''bezier' | | edgeTypes | Record<string, CanvasEdgeDefinition> | - | 额外/覆盖的边类型定义,会与内置类型合并 |

返回值

| 属性 | 类型 | 说明 | | ------------ | ------------------- | -------------------------------------------- | | schema | CanvasSchema | 可直接传给 useCanvasEditor 的 Schema 定义 | | paletteItems | NodePaletteItem[] | 可传给 CanvasLayoutpaletteItems prop |

内置节点类型

不传 nodeTypes 参数时,默认包含 7 种节点:

| 类型 | 标签 | 尺寸 | | ------------------------------ | ------------ | -------- | | start | 开始 | 88 × 40 | | end | 结束 | 88 × 40 | | empty | 空节点 | 240 × 48 | | parallel-gateway | 并行网关 | 64 × 64 | | branch-gateway | 分支网关 | 64 × 64 | | converge-gateway | 汇聚网关 | 64 × 64 | | conditional-parallel-gateway | 条件并行网关 | 64 × 64 |

每种节点均使用内置 DefaultNode 组件渲染,自带上/右/下/左四个连接端口。

自定义节点类型列表:

const { schema, paletteItems } = createDefaultSchema({
  nodeTypes: {
    task: { label: '任务节点', width: 180, height: 60 },
    approval: { label: '审批节点', icon: 'my-icon-class', width: 200, height: 60 },
    notification: { label: '通知节点', width: 160, height: 50 },
  },
});

连线类型配置

createDefaultSchema 内置两种连线类型:

| 类型 | 路由 | 连接器 | 说明 | | ------------------- | ----------- | ---------------------- | --------------------- | | manhattan(默认) | manhattan | rounded(radius: 8) | 直角折线,圆角转弯 | | bezier | 无 | smooth | 贝塞尔曲线,平滑 S 形 |

切换为贝塞尔曲线连线:

const { schema, paletteItems } = createDefaultSchema({
  defaultEdgeType: 'bezier',
});

使用自定义边类型:

const { schema, paletteItems } = createDefaultSchema({
  defaultEdgeType: 'myEdge',
  edgeTypes: {
    myEdge: {
      connector: 'normal',
      style: (_edge, state) => ({
        stroke: state.hovered ? 'red' : 'gray',
        strokeWidth: 2,
      }),
    },
  },
});

在自定义 Schema 中复用内置边类型:

当你没有使用 createDefaultSchema(),而是像适配层那样手写 CanvasSchema 时,可以通过 createBuiltinEdgeTypes() 直接复用引擎内置的 manhattan / bezier 定义,避免在业务包里重复拷贝一份。

import { createBuiltinEdgeTypes, type CanvasSchema } from '@blueking/flow-canvas';

const builtinEdgeTypes = createBuiltinEdgeTypes();

const schema: CanvasSchema = {
  nodeTypes: {
    /* ... */
  },
  edgeTypes: {
    default: { ...builtinEdgeTypes.manhattan, labelDraggable: true },
    ...builtinEdgeTypes,
  },
  defaultEdgeType: 'bezier',
};

手动定义边类型(不使用 createDefaultSchema):

在手动编写 CanvasSchema 时,同样可以通过 edgeTypesdefaultEdgeType 配置连线样式:

const schema: CanvasSchema = {
  nodeTypes: {
    /* ... */
  },
  edgeTypes: {
    bezier: {
      connector: { name: 'smooth' },
      style: (_edge, state) => ({
        stroke: state.hovered ? '#3a84ff' : '#abb5cc',
        strokeWidth: 2,
      }),
      x6EdgeConfig: {
        attrs: {
          line: {
            stroke: '#abb5cc',
            strokeWidth: 2,
            targetMarker: { name: 'block', width: 8, height: 8 },
          },
        },
      },
    },
    manhattan: {
      router: { name: 'manhattan', args: { padding: 10, maxDirectionChange: 90 } },
      connector: { name: 'rounded', args: { radius: 8 } },
      style: (_edge, state) => ({
        stroke: state.hovered ? '#3a84ff' : '#abb5cc',
        strokeWidth: 2,
      }),
    },
  },
  defaultEdgeType: 'bezier',
};

默认连线与端口样式

  • 端口:半径 6px,背景 #3a84ff,1px 白色描边。默认隐藏(visibility: hidden),hover 节点时显示;拖拽连线时自动显示所有节点端口。
  • 连线:宽度 2px,颜色 #abb5cc,hover 变 #3a84ff,平角箭头(block marker)。zIndex: -1 确保在节点下方,不阻挡端口交互。
  • 连接点connectionPoint: 'anchor' + anchor: 'center',连线直达端口中心,紧贴节点。

命令系统

所有 FlowModel 变更必须通过命令执行。每条命令通过 CommandEnvelope 包装,一个信封可包含多条原子命令,作为一次事务整体执行(撤销时整体回退)。

import { generateId } from '@blueking/flow-canvas';

添加节点和连线:

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '添加节点',
  timestamp: Date.now(),
  commands: [
    { type: 'node.add', node: { id: 'n1', type: 'task', position: { x: 100, y: 100 }, label: '审批' } },
    {
      type: 'edge.add',
      edge: { id: 'e1', source: { nodeId: 'n0', portId: 'right' }, target: { nodeId: 'n1', portId: 'left' } },
    },
  ],
});

删除节点(自动级联删除关联边):

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '删除节点',
  timestamp: Date.now(),
  commands: [{ type: 'node.remove', nodeId: 'n1' }],
});

删除连线:

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '删除连线',
  timestamp: Date.now(),
  commands: [{ type: 'edge.remove', edgeId: 'e1' }],
});

移动节点:

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '移动节点',
  timestamp: Date.now(),
  commands: [{ type: 'node.move', nodeId: 'n1', position: { x: 300, y: 200 } }],
});

更新节点结构字段(label、type、ports 等,不含 payload/extensions):

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '重命名节点',
  timestamp: Date.now(),
  commands: [{ type: 'node.update', nodeId: 'n1', patch: { label: '新名称' } }],
});

按路径更新节点 payload(业务数据):

// 设置 payload.config.timeout = 30
editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '更新超时配置',
  timestamp: Date.now(),
  commands: [{ type: 'node.set-payload', nodeId: 'n1', path: ['config', 'timeout'], value: 30 }],
});

// 设置整个 payload.config 对象
editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '更新节点配置',
  timestamp: Date.now(),
  commands: [{ type: 'node.set-payload', nodeId: 'n1', path: ['config'], value: { timeout: 30, retryable: true } }],
});

// 删除 payload 中的某个字段(value 传 undefined)
editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '清除超时配置',
  timestamp: Date.now(),
  commands: [{ type: 'node.set-payload', nodeId: 'n1', path: ['config', 'timeout'], value: undefined }],
});

重连边端点:

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '重连边',
  timestamp: Date.now(),
  commands: [
    {
      type: 'edge.reconnect',
      edgeId: 'e1',
      target: { nodeId: 'n2', portId: 'left' }, // 只改 target,source 不变
    },
  ],
});

更新边标签:

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '更新边标签',
  timestamp: Date.now(),
  commands: [{ type: 'edge.label.update', edgeId: 'e1', labelId: 'label1', patch: { text: '条件成立' } }],
});

更新全局 meta:

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '更新全局变量',
  timestamp: Date.now(),
  commands: [{ type: 'model.set-meta', path: ['variables', 'env'], value: 'production' }],
});

批量操作(一个信封包含多条命令,一次性执行):

editor.executeCommand({
  id: generateId(),
  source: 'user:panel',
  label: '批量删除选中',
  timestamp: Date.now(),
  commands: [
    { type: 'edge.remove', edgeId: 'e1' },
    { type: 'edge.remove', edgeId: 'e2' },
    { type: 'node.remove', nodeId: 'n1' },
    { type: 'node.remove', nodeId: 'n2' },
  ],
});

处理命令执行结果:

const result = editor.executeCommand({
  /* ... */
});

if (result.status === 'applied') {
  console.log('执行成功,新模型:', result.flowModel);
} else if (result.status === 'rejected') {
  console.warn('被插件拒绝:', result.error?.reason);
} else if (result.status === 'invalid') {
  console.error('违反约束:', result.error?.reason);
}

命令类型一览

| 命令 | 说明 | | --------------------- | ------------------------------------------- | | node.add | 添加节点 | | node.move | 移动节点位置 | | node.remove | 删除节点(自动级联删除关联边) | | node.update | 更新节点结构字段(不含 payload/extensions) | | node.set-payload | 按路径更新节点 payload | | node.set-extensions | 按路径更新节点 extensions | | edge.add | 添加边 | | edge.remove | 删除边 | | edge.reconnect | 重连边端点 | | edge.update | 更新边结构字段 | | edge.set-payload | 按路径更新边 payload | | edge.label.update | 更新边标签 | | model.set-meta | 按路径更新全局 meta |

path 参数使用 string[] 格式:{ path: ['config', 'timeout'], value: 30 }

CommandSource 类型

'user:drag' | 'user:keyboard' | 'user:toolbar' | 'user:quick-add' | 'user:panel' | 'plugin' | 'system' | 'system:replace'

组件

CanvasRuntime

核心渲染组件,负责创建 X6 Graph 并双向桥接 FlowModel。

| Prop | 类型 | 说明 | | ---------------------------- | -------------------------------------------- | ---------------------------------------------------- | | editor | CanvasEditorContext | 编辑器实例(必填) | | graphOptions | Record<string, unknown> | 额外 X6 Graph 选项 | | nodeActions | NodeActionsConfig | 节点快捷操作工具栏配置(见下方详细说明) | | quickAdd | QuickAddConfig | 快捷添加按钮配置(默认绑定右侧端口,见下方详细说明) | | getConnectionExcludedNodeIds | (sourceNodeId: string) => Iterable<string> | 拖拽连线时返回不应显示端口的节点 ID 集合 |

| Emit | 参数 | 说明 | | -------- | --------------- | -------------------- | | ui-event | CanvasUiEvent | 非命令类 UI 交互事件 |

| Slot | Props | 说明 | | --------------- | ------------------------------------------------ | ------------------------------------------------------------------- | | quick-add-panel | { node, api, insertNodeToRight, closePopover } | 快捷添加弹层内容,点击绑定端口上的"+"按钮时显示(默认显示提示文字) |

节点快捷操作工具栏

CanvasRuntime 内置节点快捷操作工具栏,hover 节点时在右下角显示。通过 nodeActions prop 进行全局配置:

<CanvasRuntime :editor="editor" :node-actions="{ showDebug: true, showCopy: false }" />

NodeActionsConfig(全局开关)

| 属性 | 类型 | 默认 | 说明 | | ---------------- | --------- | ------- | ---------------------------- | | showDebug | boolean | false | 是否显示调试按钮 | | showDelete | boolean | true | 是否显示删除按钮 | | showCopy | boolean | true | 是否显示复制按钮 | | showCopyInsert | boolean | true | 是否显示复制并插入按钮 | | showDisconnect | boolean | true | 是否显示断开连线按钮 | | insertGap | number | 100 | 向右插入节点的水平间距(px) |

NodeBehaviorConfig 中的工具栏相关字段(逐节点控制)

通过 schema getBehavior 返回值控制每个节点类型的工具栏行为:

| 属性 | 说明 | | --------------------- | --------------------------------------------------------------------------------- | | showActions | false → 该节点类型不显示整个工具栏 | | deletable | false → 隐藏删除按钮 | | copyable | false → 隐藏复制和复制并插入按钮 | | disconnectable | false → 隐藏断开连线按钮 | | debuggable | false → 隐藏调试按钮(需全局 showDebug 也为 true) | | deleteDisabled | true → 删除按钮可见但置灰不可点击 | | copyDisabled | true → 复制按钮置灰 | | copyInsertDisabled | true → 复制并插入按钮置灰 | | disconnectDisabled | true → 断开连线按钮置灰 | | debugDisabled | true → 调试按钮置灰 | | connectable | false → 此节点端口不允许发起连线(控制 magnet 属性) | | targetable | false → 拖拽连线期间不显示此节点端口(不可作为连线目标) | | bringToFrontOnDrag | false → 拖拽时不临时置顶节点 z-index(默认 true) | | hidePortForQuickAdd | false → quick-add 激活时不隐藏对应方向的连接端口(默认 true) | | actionsOffset | { x?: number; y?: number } — 工具栏位置偏移(px),相对于默认位置(节点右下角) |

按钮有效可见性 = 全局 show* AND 逐节点 *able !== false; 按钮禁用状态 = 逐节点 *Disabled === true

操作触发后通过 @ui-event 发出对应事件:node.action.delete / node.action.copy / node.action.copy-insert / node.action.disconnect / node.action.debug

节点快捷添加按钮(Quick Add)

hover 节点时,指定端口会显示为"+"按钮,支持点击弹出面板和拖拽发起连线。默认绑定右侧 group: 'right' 端口。通过 quickAdd prop 进行全局配置:

<CanvasRuntime :editor="editor" :quick-add="{ enabled: true, portGroup: 'right' }">
  <template #quick-add-panel="{ node, api, insertNodeToRight, closePopover }">
    <!-- 自定义节点选择面板 -->
  </template>
</CanvasRuntime>

QuickAddConfig(全局配置)

| 属性 | 类型 | 默认 | 说明 | | ----------------- | -------------------- | --------- | ------------------------------------------------------------------------------------------------- | | enabled | boolean | true | 全局开关 | | portGroup | string | 'right' | quick-add 默认绑定的端口分组;节点不存在该分组端口时不显示 quick-add | | getPort | function | - | 精确指定当前节点使用哪个 port;优先级高于 portGroup | | insertDirection | string \| function | - | 点击 quick-add 插入节点时使用的方向;默认跟随当前绑定 port 的标准方向,不可推断时回退为 'right' | | autoPanOnOpen | boolean | true | 点击打开 quick-add 面板时,如弹层会被浏览器右侧遮住,则自动左移画布使其完整显示 | | autoPanPadding | number | 16 | autoPanOnOpen 生效时,弹层与浏览器右边缘保留的安全间距(px) |

NodeBehaviorConfig 中的快捷添加相关字段(逐节点控制)

| 属性 | 说明 | | ----------------- | --------------------------------------------------------------- | | quickAddEnabled | false → 该节点不显示"+"按钮(如结束节点、已有出边的开始节点) |

生效规则:显示 = global.enabled !== false AND perNode.quickAddEnabled !== false

对于未显式提供 getPorts / node.ports 的自定义节点,quick-add 会落到引擎自动补齐的默认四向端口上,因此默认右侧交互仍然可用。

getPort(node, ports) 优先级高于 portGroup,适合一个节点存在多个同分组 port,或端口选择依赖节点数据的场景。

insertDirection 用于控制点击 quick-add 后的插入方向:

  • 不传时:优先跟随当前绑定 port 的标准方向(top/right/bottom/left
  • 如果当前绑定 port 不是标准方向:回退为 'right'
  • 传字符串时:固定使用该方向
  • 传函数时:可根据节点和当前绑定 port 动态决定方向

portGroup / getPort 只控制 quick-add 绑定到哪个端口、"+"按钮显示在哪个端口上,以及拖拽从哪个端口发起。

slot 中提供的 insertNodeToRight helper 出于兼容仍保留这个名字,但它现在会遵循 insertDirection 的配置,不再总是向右插入。

自定义 quick-add 端口选择:

<CanvasRuntime
  :editor="editor"
  :quick-add="{
    enabled: true,
    portGroup: 'right',
    getPort: (node, ports) => {
      if (node.type === 'gateway') {
        return ports.find((port) => port.id === 'branch_out_main') ?? null;
      }
      return null; // 返回 null 时退回使用 portGroup
    },
    insertDirection: (node, port) => {
      if (node.type === 'gateway' && port?.group === 'bottom') {
        return 'bottom';
      }
      return null; // 返回 null 时按默认推导
    },
  }" />

quick-add-panel Slot Props

| Prop | 类型 | 说明 | | ------------------- | ------------------------------------------------- | ---------------------------------------------------------------- | | node | FlowNodeModel | 当前触发的源节点 | | api | CanvasApi | 画布 API | | insertNodeToRight | (node: Omit<FlowNodeModel, 'position'>) => void | 快捷插入方法(自动连线 + 事件);方向遵循 insertDirection 配置 | | closePopover | () => void | 关闭弹层 |

操作触发后通过 @ui-event 发出对应事件:node.quick-add(弹层打开时)/ node.action.quick-insert(插入成功时)。

完整的 quick-add-panel 使用示例:

<template>
  <CanvasLayout :editor="editor" :palette-items="paletteItems">
    <CanvasRuntime :editor="editor" :quick-add="{ enabled: true, portGroup: 'right' }" @ui-event="handleUiEvent">
      <template #quick-add-panel="{ node, insertNodeToRight, closePopover }">
        <div class="quick-add-menu">
          <div class="quick-add-menu__title">选择节点类型</div>
          <div
            v-for="item in quickAddNodeList"
            :key="item.type"
            class="quick-add-menu__item"
            @click="handleQuickAdd(item, insertNodeToRight, closePopover)">
            <i :class="item.icon" />
            <span>{{ item.label }}</span>
          </div>
        </div>
      </template>
    </CanvasRuntime>
    <CanvasToolbar :editor="editor" />
  </CanvasLayout>
</template>

<script setup lang="ts">
  import {
    useCanvasEditor,
    CanvasRuntime,
    CanvasLayout,
    CanvasToolbar,
    createDefaultSchema,
    createEmptyFlowModel,
    generateId,
    type FlowNodeModel,
  } from '@blueking/flow-canvas';
  import '@blueking/flow-canvas/style';

  const { schema, paletteItems } = createDefaultSchema();
  const editor = useCanvasEditor({ initialFlowModel: createEmptyFlowModel(), schema });

  const quickAddNodeList = [
    { type: 'empty', label: '空节点', icon: 'flow-canvas-icon canvas-jiedi' },
    { type: 'parallel-gateway', label: '并行网关', icon: 'flow-canvas-icon canvas-bingxingwangguan' },
  ];

  function handleQuickAdd(
    item: { type: string; label: string },
    insertNodeToRight: (node: Omit<FlowNodeModel, 'position'>) => void,
    closePopover: () => void,
  ) {
    insertNodeToRight({
      id: generateId(),
      type: item.type,
      label: item.label,
    });
    closePopover();
  }
</script>

通过 getBehavior 可动态控制哪些节点显示"+"按钮:

const schema: CanvasSchema = {
  nodeTypes: {
    end: {
      component: EndNode,
      getSize: () => ({ width: 88, height: 40 }),
      getBehavior: () => ({
        quickAddEnabled: false, // 结束节点不显示"+"按钮
        deletable: false,
      }),
    },
    task: {
      component: TaskNode,
      getSize: () => ({ width: 180, height: 60 }),
      getBehavior: (node, ctx) => {
        const hasOutEdge = Object.values(ctx.flowModel.edges).some((e) => e.source.nodeId === node.id);
        return {
          quickAddEnabled: !hasOutEdge, // 已有出边时隐藏"+"按钮
        };
      },
    },
  },
};

CanvasLayout

可选的布局骨架组件,提供侧边栏 + 主画布 + 底栏的三栏布局。

| Prop | 类型 | 默认 | 说明 | | ---------------- | --------------------- | ----------- | ---------------------------------------------------- | | sidebarCollapsed | boolean | false | 侧边栏是否收起 | | sidebarWidth | number | 260 | 侧边栏宽度(px) | | hideSidebar | boolean | false | 是否隐藏侧边栏 | | hideFooter | boolean | false | 是否隐藏底部栏 | | editor | CanvasEditorContext | undefined | 编辑器实例,传入后自动渲染内置 CanvasNodePalette | | paletteItems | NodePaletteItem[] | undefined | 节点面板项列表,配合 editor 使用自定义面板节点列表 |

| Emit | 参数 | 说明 | | ----------------------- | --------- | ------------------ | | update:sidebarCollapsed | boolean | 侧边栏收起状态变更 |

| Slot | 说明 | | ------- | --------------------------------------------------------- | | sidebar | 侧边栏内容(传入后覆盖内置 CanvasNodePalette 默认面板) | | default | 主画布区域(放置 CanvasRuntime + CanvasToolbar) | | footer | 底部栏 |

侧边栏的三种使用方式

方式一:自动渲染内置节点面板

传入 editor prop 且不提供 sidebar slot,CanvasLayout 自动在侧边栏渲染 CanvasNodePalette,用户可从面板拖拽节点到画布:

<CanvasLayout :editor="editor" :palette-items="paletteItems">
  <CanvasRuntime :editor="editor" />
</CanvasLayout>

通过 paletteItems prop 自定义面板中显示的节点类型列表:

<script setup lang="ts">
  import { createDefaultSchema, useCanvasEditor, createEmptyFlowModel } from '@blueking/flow-canvas';

  const { schema, paletteItems } = createDefaultSchema();
  const editor = useCanvasEditor({ initialFlowModel: createEmptyFlowModel(), schema });

  // 也可以手动指定面板项,只展示部分节点类型
  const customPaletteItems = [
    { type: 'start', label: '开始节点', icon: 'flow-canvas-icon canvas-kaishi' },
    { type: 'end', label: '结束节点', icon: 'flow-canvas-icon canvas-stop' },
    { type: 'empty', label: '空节点', icon: 'flow-canvas-icon canvas-jiedi' },
  ];
</script>

<template>
  <CanvasLayout :editor="editor" :palette-items="customPaletteItems">
    <CanvasRuntime :editor="editor" />
  </CanvasLayout>
</template>

方式二:通过 sidebar slot 完全自定义侧边栏

<template>
  <CanvasLayout>
    <template #sidebar>
      <div class="my-sidebar">
        <h3>节点库</h3>
        <div v-for="item in nodeItems" :key="item.type" ref="dndRefs" class="my-sidebar__item" :data-type="item.type">
          {{ item.label }}
        </div>
      </div>
    </template>
    <CanvasRuntime :editor="editor" />
  </CanvasLayout>
</template>

<script setup lang="ts">
  import { ref, watch } from 'vue';
  import { useCanvasEditor, CanvasRuntime, CanvasLayout, generateId } from '@blueking/flow-canvas';

  const nodeItems = [
    { type: 'task', label: '任务' },
    { type: 'approval', label: '审批' },
  ];

  // 通过 api.registerDndSource 注册拖拽源
  watch(
    () => editor.api.value,
    (api) => {
      if (!api) return;
      for (const item of nodeItems) {
        const el = document.querySelector(`[data-type="${item.type}"]`);
        if (el) {
          api.registerDndSource(el as HTMLElement, () => ({
            id: generateId(),
            type: item.type,
            label: item.label,
            position: { x: 0, y: 0 },
          }));
        }
      }
    },
  );
</script>

方式三:隐藏侧边栏

<CanvasLayout hide-sidebar>
  <CanvasRuntime :editor="editor" />
</CanvasLayout>

CanvasNodePalette

内置的节点面板组件,支持拖拽节点到画布(DnD)。通常由 CanvasLayout 自动渲染,也可以单独使用。

| Prop | 类型 | 必填 | 说明 | | ------ | --------------------- | ---- | ---------------------------------------- | | editor | CanvasEditorContext | 是 | 编辑器实例 | | items | NodePaletteItem[] | 否 | 节点列表,不传时从 schema.nodeTypes 生成 |

interface NodePaletteItem {
  type: string; // 节点类型,对应 schema.nodeTypes 的 key
  label: string; // 显示名称
  icon?: string; // CSS 图标类名
}

CanvasToolbar

可选的工具栏组件。默认浮动在画布左上角,内置下载、缩放、重置等核心操作;search / minimap 由对应插件注入后显示。

| Prop | 类型 | 默认 | 说明 | | ------- | ---------------------- | ---------- | --------------------------------------- | | editor | CanvasEditorContext | - | 编辑器实例(必填) | | items | CanvasToolbarItem[] | 内置默认项 | 工具栏项列表,传入后完全替换默认项 | | exclude | BuiltinToolbarType[] | - | 排除特定默认项(仅在未传 items 时生效) |

createDefaultToolbarItems(options?)

工厂函数,用于获取默认工具栏项列表,便于自定义组合。

撤销/重做(undo / redo)默认隐藏,可通过 include 恢复显示。

import { createDefaultToolbarItems } from '@blueking/flow-canvas';

// 默认不含撤销/重做
const items = createDefaultToolbarItems();

// 恢复显示撤销/重做
const itemsWithHistory = createDefaultToolbarItems({ include: ['undo', 'redo'] });

// 恢复撤销/重做,追加自定义项
const customItems = [
  ...createDefaultToolbarItems({ include: ['undo', 'redo'] }),
  { id: 'my-tool', type: 'custom', icon: 'my-icon-class', onClick: handleClick },
];

CanvasToolbarItem 字段

| 字段 | 类型 | 说明 | | ----------- | -------------------------------------- | ----------------------------------------- | | id | string | 唯一标识 | | type | BuiltinToolbarType \| 'custom' | 操作类型 | | icon | string | CSS 类名(字体图标) | | component | Component | Vue 组件,渲染在按钮内部作为图标/内容区域 | | text | string | 无图标时的兜底显示文字 | | description | string | 操作说明,有值时 hover 显示 tooltip 气泡 | | onClick | (ctx: CanvasCallbackContext) => void | 点击回调 |

通过 CSS 变量定制工具栏样式与位置

CanvasToolbar 通过 CSS 自定义属性暴露了位置和外观的定制接口,使用方在父元素上设置对应变量即可覆盖默认值:

| CSS 变量 | 默认值 | 说明 | | --------------------------------- | --------------------------- | -------------------- | | --flow-canvas-toolbar-top | 16px | 工具栏距画布顶部距离 | | --flow-canvas-toolbar-left | 16px | 工具栏距画布左侧距离 | | --flow-canvas-toolbar-right | auto | 工具栏距画布右侧距离 | | --flow-canvas-toolbar-bottom | auto | 工具栏距画布底部距离 | | --flow-canvas-toolbar-bg | #565e7a | 工具栏背景色 | | --flow-canvas-toolbar-color | #f5f7fa | 工具栏文字/图标颜色 | | --flow-canvas-toolbar-hover-bg | rgba(255, 255, 255, 0.15) | 按钮 hover 背景色 | | --flow-canvas-toolbar-active-bg | rgba(255, 255, 255, 0.25) | 按钮激活态背景色 |

示例 — 将工具栏移至画布底部居中,并更换背景色:

<template>
  <CanvasLayout :editor="editor" :palette-items="paletteItems" class="my-canvas-wrapper">
    <CanvasRuntime :editor="editor" />
    <CanvasToolbar :editor="editor" />
  </CanvasLayout>
</template>

<style scoped>
  .my-canvas-wrapper {
    --flow-canvas-toolbar-top: auto;
    --flow-canvas-toolbar-bottom: 16px;
    --flow-canvas-toolbar-left: 50%;
    --flow-canvas-toolbar-right: auto;
    --flow-canvas-toolbar-bg: #1a1a2e;
  }
</style>

自定义节点组件

节点的渲染由 CanvasNodeDefinition.component 指定的 Vue 组件控制。组件内可通过 inject('getNode') 获取 X6 Node 实例,读取 FlowNodeModel 数据。

基础节点组件

<!-- my-task-node.vue -->
<template>
  <div class="my-task-node" :class="{ 'is-selected': selected }">
    <i v-if="icon" :class="icon" class="my-task-node__icon" />
    <span class="my-task-node__label">{{ label }}</span>
  </div>
</template>

<script setup lang="ts">
  import { inject, ref, onMounted, onBeforeUnmount, computed } from 'vue';
  import type { FlowNodeModel } from '@blueking/flow-canvas';

  const getNode = inject<() => any>('getNode')!;

  const nodeModel = ref<FlowNodeModel>();
  const selected = ref(false);

  const label = computed(() => nodeModel.value?.label ?? '');
  const icon = computed(() => (nodeModel.value?.extensions as any)?.icon ?? '');

  onMounted(() => {
    const node = getNode();
    nodeModel.value = node.getData() as FlowNodeModel;

    node.on('change:data', ({ current }: { current: FlowNodeModel }) => {
      nodeModel.value = current;
    });
  });
</script>

<style scoped>
  .my-task-node {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    height: 100%;
    padding: 0 16px;
    background: #fff;
    border: 1px solid #dcdee5;
    border-radius: 4px;
    box-sizing: border-box;
    cursor: pointer;
  }
  .my-task-node.is-selected {
    border-color: #3a84ff;
  }
</style>

在 Schema 中注册节点组件

import { createBuiltinEdgeTypes } from '@blueking/flow-canvas';
import MyTaskNode from './components/my-task-node.vue';
import MyGatewayNode from './components/my-gateway-node.vue';

const builtinEdgeTypes = createBuiltinEdgeTypes();

const schema: CanvasSchema = {
  nodeTypes: {
    task: {
      component: MyTaskNode,
      getSize: () => ({ width: 180, height: 60 }),
      getBehavior: (node, ctx) => ({
        deletable: true,
        copyable: true,
        quickAddEnabled: true,
      }),
    },
    gateway: {
      component: MyGatewayNode,
      getSize: () => ({ width: 64, height: 64 }),
      getPorts: () => [
        { id: 'top', group: 'top' },
        { id: 'right', group: 'right' },
        { id: 'bottom', group: 'bottom' },
        { id: 'left', group: 'left' },
      ],
      getBehavior: () => ({
        copyable: false,
        showActions: true,
      }),
    },
  },
  edgeTypes: {
    bezier: builtinEdgeTypes.bezier,
  },
  defaultEdgeType: 'bezier',
};

上面这个例子里,task 故意不写 getPorts,直接复用引擎默认四向端口;gateway 保留 getPorts,用于演示“当节点需要显式控制端口拓扑时再覆盖默认行为”。

CanvasApi

editor.api.valueCanvasRuntime 挂载后可用,之前为 null

| 方法 | 说明 | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | zoomIn() / zoomOut() | 缩放 | | zoomTo(value) | 缩放到指定比例 | | zoomToFit() | 自适应画布 | | getZoom() | 获取当前缩放比例 | | centerContent() | 居中画布内容 | | scrollToNode(nodeId) | 滚动到指定节点 | | getSelection() | 获取当前选区 { nodeIds, edgeIds } | | getSelectionBounds(selection?) | 获取当前选区或指定选区的整体包围盒,用于挂载选区工具条等 overlay | | selectNodes(ids) | 选中指定节点 | | selectEdges(ids) | 选中指定边 | | clearSelection() | 清除选区 | | deleteSelection(options?) | 统一删除当前选区或指定选区;按 schema behavior 过滤不可删元素,默认成功后清空选区 | | registerDndSource(el, factory) | 注册 DND 拖拽源,返回清理函数 | | startConnection(nodeId, portId?) | 从指定节点/端口发起连线 | | highlightNodes(ids) / highlightEdges(ids) | 高亮指定元素 | | clearHighlight() | 清除所有高亮 | | exportAsImage(options?) | 导出为 PNG Blob(基于 modern-screenshot,需安装为 peer dependency) | | overlay | OverlayManager 实例,提供节点屏幕定位 | | insertNodeToRight(sourceNodeId, node, options?) | 默认在指定节点右侧插入新节点;传 options.direction 后可改为上/右/下/左方向,并支持自动连线和重叠检测 | | getContextMenuItems(position) | 收集插件提供的右键菜单项 | | onGraphEvent(event, handler) | 监听 X6 底层事件,返回取消函数 | | unsafeGetGraph() | 获取 X6 Graph 实例(谨慎使用) |

内置插件

框选模式

selectionMode 只表示“是否启用框选模式”,不等价于“是否允许节点被选中”。

  • selectionMode = true 空白区域左键拖拽启用 rubberband 框选。
  • selectionMode = false 空白区域左键拖拽恢复为画布平移。
  • 无论 selectionMode 是否开启 普通点击选中、selectNodes()selectEdges() 仍然可用。
editor.setSelectionMode(true);
editor.setSelectionMode(false);

selectionPlugin(options?)

接入 X6 的通用选区能力,支持多选、框选和 _selectable 节点过滤。

selectionPlugin({
  rubberband: true, // 框选,默认 true
  multiple: true, // 多选,默认 true
  movable: true, // 选中后可拖拽移动,默认 true
});

注意:

  • selectionPlugin() 负责注册 X6 Selection 能力。
  • selectionModeCanvasRuntime 在运行时控制 rubberband / panning 的切换。
  • selectionPlugin() 本身不负责决定当前是否处于框选模式。

snaplinePlugin(options?)

节点拖拽时显示对齐辅助线。

snaplinePlugin({ tolerance: 10 }); // 对齐容差,默认 10

connectionValidatorPlugin(validator)

连线校验。

connectionValidatorPlugin((ctx) => {
  // ctx.flowModel: 当前 FlowModel
  // ctx.sourceNode / targetNode: 源/目标节点
  // ctx.existingEdges: 已有边列表
  if (ctx.sourceNode.id === ctx.targetNode.id) {
    return { valid: false, reason: '不允许自环' };
  }
  return { valid: true };
});

searchPlugin(options?)

工具栏搜索节点。点击搜索图标后弹出面板,支持按关键字过滤节点并通过 Enter / 点击快速定位到目标节点。

searchPlugin({
  placeholder: '搜索节点名称',
  getNodeMeta(node) {
    const stageName = typeof node.payload?.stageName === 'string' ? node.payload.stageName : '';
    return {
      label: node.label ?? '',
      subtitle: stageName || undefined,
      keywords: [node.label ?? '', stageName],
    };
  },
});

minimapPlugin(options?)

小地图。传入 container 后启用。

minimapPlugin({
  container: document.getElementById('minimap'),
  width: 200,
  height: 160,
});

clipboardPlugin()

Cmd/Ctrl+C 复制选中节点/边,Cmd/Ctrl+V 粘贴(生成新 ID、偏移位置)。

自定义插件

const myPlugin: CanvasPlugin = {
  name: 'my-plugin',
  priority: 50,

  // editor 阶段:仅有 flowModel / history / schema
  install(ctx) {},

  // runtime 阶段:api / graph 已可用
  attachRuntime(ctx) {
    ctx.api.onGraphEvent('cell:mouseenter', () => {
      /* ... */
    });
  },
  detachRuntime() {},

  // 命令拦截/增强(不可变管道)
  transformCommand(envelope, preview, ctx) {
    // 返回新 envelope = 修改后的命令
    // 返回 { rejected: true, reason: '...' } = 拒绝
    // 返回 null = 丢弃
    return envelope;
  },

  // 命令执行后
  afterCommand(envelope, prevModel, newModel, ctx) {},

  // UI 事件
  onUiEvent(event, ctx) {},
  onKeyboardShortcut(event, ctx) {
    return false;
  },
  onSelectionChange(selection, ctx) {},

  // 视觉装饰
  decorateNode(node, ctx) {
    if (node.payload?.hasError) {
      return { borderColor: '#ff4d4d', badge: { text: '!', color: '#ff4d4d' } };
    }
  },
  decorateEdge(edge, ctx) {},

  // 工具栏扩展
  provideToolbarItems(ctx) {
    return [{ id: 'my-tool', type: 'custom', text: '自定义工具', onClick: () => {} }];
  },

  // API 扩展
  extendApi(api, ctx) {
    return {
      doSomething: () => {
        /* ... */
      },
    };
  },
};

工具函数与底层 API

| 导出 | 说明 | | ------------------------------------------------- | --------------------------------------------------------------------------------- | | createEmptyFlowModel() | 创建一个空的 FlowModel(version: '1.0', 空 nodes/edges) | | createBuiltinEdgeTypes() | 返回引擎内置的 manhattan / bezier 边类型定义,适合自定义 Schema 直接复用 | | generateId() | 生成唯一 ID | | applyCanvasCommand(model, cmd) | 纯函数 reducer,将单条命令应用到 FlowModel,返回新 FlowModel | | createCanvasHistory(initialFlowModel, options?) | 创建独立的历史管理器实例(通常由 useCanvasEditor 内部调用,高级场景可直接使用) | | IdGenerator | (type: 'node' \| 'edge') => string,自定义 ID 生成函数类型 | | CanvasConstraintError | 命令违反 FlowModel 不变量时抛出的错误类(如重复 ID、悬挂引用等) | | CanvasSchemaError | 未知节点/边类型等 Schema 级错误类 |

import { createEmptyFlowModel, generateId } from '@blueking/flow-canvas';

const emptyModel = createEmptyFlowModel();
// { version: '1.0', nodes: {}, edges: {} }

撤销/重做

editor.history.undo();
editor.history.redo();
editor.history.canUndo.value; // boolean
editor.history.canRedo.value; // boolean
editor.history.clear();

事件类型 (CanvasUiEvent)

| type | 附加字段 | 说明 | | ------------------------ | ----------------------- | ---------------------- | | node.click | nodeId | 节点点击 | | node.dblclick | nodeId | 节点双击 | | node.contextmenu | nodeId, position | 节点右键 | | edge.click | edgeId | 边点击 | | edge.label.click | edgeId, labelId | 边标签点击 | | blank.click | position | 空白区域点击 | | blank.contextmenu | position | 空白区域右键 | | selection.change | nodeIds, edgeIds | 选区变更 | | node.action.delete | nodeId | 快捷删除节点 | | node.action.copy | sourceNodeId, newNodeId | 快捷复制节点 | | node.action.copy-insert | sourceNodeId, newNodeId | 快捷复制并插入 | | node.action.disconnect | nodeId, edgeIds | 快捷断开连线 | | node.action.debug | nodeId | 快捷调试 | | node.quick-add | nodeId, position | 快捷添加弹层打开 | | node.action.quick-insert | sourceNodeId, newNodeId | 快捷插入节点 | | toolbar.auto-layout | — | 自动排版(由外层实现) |

公开类型参考

以下类型从包入口公开导出,供 TypeScript 接入方使用。

数据模型

| 类型 | 说明 | | -------------------- | ---------------------------- | | FlowModel | 流程模型,编辑态唯一权威状态 | | FlowNodeModel | 节点模型 | | FlowEdgeModel | 边模型 | | FlowPortModel | 端口模型 | | FlowEdgeLabelModel | 边标签模型 |

Schema 与渲染

| 类型 | 说明 | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | CanvasSchema | 画布配置(nodeTypes / edgeTypes) | | CanvasNodeDefinition | 节点类型定义(component / getSize / getPorts / getBehavior) | | CanvasEdgeDefinition | 边类型定义(router / connector / style / labelDraggable)。labelRenderer 字段为 @experimental,当前未实现 | | CanvasCallbackContext | 运行时上下文(api / flowModel / history / mode) | | CanvasMode | 模式:'edit' / 'readonly' / 'thumbnail' | | NodeBehaviorConfig | 节点行为配置(showActions / draggable / selectable / deletable / connectable / targetable / copyable / disconnectable / debuggable / quickAddEnabled / bringToFrontOnDrag / hidePortForQuickAdd + 各按钮 Disabled 状态) | | EdgeRenderState | 边渲染状态(selected / highlighted / hovered) | | EdgeStyle | 边样式(stroke / strokeWidth / strokeDasharray) |

命令系统

| 类型 | 说明 | | ----------------------------------- | --------------------------------------------- | | CanvasCommand | 原子命令联合类型(node.add / edge.remove 等) | | CommandEnvelope | 命令信封(id / source / commands[]) | | CommandSource | 命令来源标识 | | CommandExecutionResult | 命令执行结果 | | CommandExecutionError | 命令执行错误详情 | | CommandPreview | 命令预览接口(用于 transformCommand) | | CommandRejection | 命令拒绝结果 | | FlowModelChangeEvent | FlowModel 变更事件 | | CanvasUiEvent | UI 交互事件联合类型 | | ScreenPosition / CanvasPosition | 坐标类型 |

插件系统

| 类型 | 说明 | | ---------------------- | -------------------------------------------------------------------------- | | CanvasPlugin | 插件接口(install / attachRuntime / transformCommand / decorateNode 等) | | EditorPluginContext | editor 阶段插件上下文(flowModel / history / schema / executeCommand) | | RuntimePluginContext | runtime 阶段插件上下文(继承 EditorPluginContext + api / graph / overlay) | | CanvasSelection | 选区数据(nodeIds / edgeIds) | | NodeDecoration | 节点装饰(className / badge / borderColor) | | EdgeDecoration | 边装饰(className / strokeColor) | | ContextMenuItem | 右键菜单项 |

API 与工具栏

| 类型 | 说明 | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | CanvasApi | 画布公共 API 接口(含 insertNodeToRight) | | CanvasToolbarItem | 工具栏项配置 | | BuiltinToolbarType | 内置工具类型('undo' / 'redo' / 'select' / 'auto-layout' / 'search' / 'minimap' / 'export' / 'zoom-in' / 'zoom-out' / 'reset' 等) | | NodeActionsConfig | 节点快捷操作工具栏全局配置(showDebug / showDelete / showCopy / showCopyInsert / showDisconnect / insertGap) | | QuickAddConfig | 快捷添加按钮全局配置(enabled / portGroup / getPort / insertDirection) | | QuickAddPortResolver | quick-add 端口选择回调类型;返回具体 port 后,优先级高于 portGroup | | QuickAddInsertDirectionResolver | quick-add 插入方向回调类型;返回具体方向后,优先级高于默认方向推导 | | InsertDirection | 节点插入方向类型('top' \| 'right' \| 'bottom' \| 'left') | | InsertNodeOptions | insertNodeToRight 选项(autoWireEdges / gap / source / label / direction) | | ExportOptions | 导出选项(backgroundColor / padding / quality / scale) | | DeleteSelectionOptions | deleteSelection 选项(selection / source / label / clearSelectionAfterApply) | | ConnectionValidator | 连接校验函数类型 | | ConnectionValidateContext | 连接校验上下文 | | ConnectionValidateResult | 连接校验结果 | | NodePaletteItem | 节点面板项(type / label / icon) |

默认 Schema

| 类型 | 说明 | | ---------------------------- | ---------------------------------- | | DefaultNodeTypeConfig | createDefaultSchema 的节点配置项 | | DefaultSchemaOptions | createDefaultSchema 的选项 | | DefaultSchemaResult | createDefaultSchema 的返回值 | | DefaultToolbarItemsOptions | createDefaultToolbarItems 的选项 |

历史与 Overlay

| 类型 | 说明 | | ---------------------- | -------------------------- | | CanvasHistory | 历史管理器接口 | | CanvasHistoryOptions | 历史配置(maxHistorySize) | | OverlayManager | Overlay 管理器接口 |

编辑器

| 类型 | 说明 | | --------------------- | ------------------------------ | | CanvasEditorContext | useCanvasEditor() 返回值类型 | | CanvasEditorOptions | useCanvasEditor() 参数类型 |

插件选项

| 类型 | 说明 | | ------------------------ | -------------------- | | SelectionPluginOptions | selectionPlugin 配置 | | SnaplinePluginOptions | snaplinePlugin 配置 | | SearchPluginOptions | searchPlugin 配置 | | MinimapPluginOptions | minimapPlugin 配置 |