23 KiB
工作流画布交互设计调研报告
版本:V2.0 | 最后更新:2026-08-17 目标产品:eai_agentplatform-app · 数字员工平台 · 工坊(Studio)
目录
- 调研范围与背景
- 行业产品全景对比
- 节点体系设计
- 3.1 三节点 vs 五节点体系
- 3.2 子类型(SubType)架构
- 3.3 连接器作为被引用资源
- 画布交互模型
- 4.1 拖入创建(Drag & Drop)
- 4.2 右键菜单体系
- 4.3 键盘快捷键体系
- 4.4 选中与视觉反馈
- 4.5 Edge 交互(连线)
- 布局与导航
- 技术实现(VueFlow)
- 6.1 核心事件
- 6.2 自定义节点
- 6.3 自定义边
- 6.4 CSS 主题与样式
- 当前实现状态
- 待完成项
- 参考资料
1. 调研范围与背景
1.1 调研目标
为数字员工平台的 工坊(Studio) 模块设计一套符合行业标准、贴近业务用户心智的工作流画布交互模型。核心需求:
- 业务用户能通过 拖拽 + 配置 的方式定义数字员工的工作流程
- 画布交互对标 Dify / n8n / Make / Zapier / Coze 等主流产品
- 节点体系从 SY18(信源/动作/结果/权限)简化为 3 节点体系(开始/动作/结束)
1.2 调研的产品
| 产品 | 定位 | 节点体系 | 画布引擎 | 参考版本 |
|---|---|---|---|---|
| Dify | AI 工作流编排 | 开始/LLM/工具/结束等 | React Flow | v1.14.0 |
| n8n | 自动化工作流 | 触发器/动作 | 自研 | latest |
| Make | 可视化自动化 | 触发器/动作/模块 | 自研 | latest |
| Zapier | 轻量自动化 | Trigger/Action/Search | 自研 | latest |
| Coze | AI Bot 构建 | 开始/LLM/插件/知识库/结束 | 自研 | latest |
| Langflow | LLM 流程编排 | Input/LLM/Output | React Flow | latest |
| VueFlow | 画布引擎库 | N/A | Vue Flow | v1.44+ |
2. 行业产品全景对比
2.1 产品定位对比
| 维度 | Dify | n8n | Make | Zapier | Coze | 本产品 |
|---|---|---|---|---|---|---|
| 目标用户 | 开发者/AI 工程师 | 技术运营 | 业务用户 | 业务用户 | 业务用户+开发者 | HR/销售/业务用户 |
| 复杂度 | 中高 | 高 | 中 | 低 | 中 | 中 |
| 拖入创建 | ✅ | ✅ | ✅ | ❌(点击添加) | ✅ | ✅ |
| 右侧检查器 | ✅ | ✅ | ✅ | ✅(弹窗) | ✅ | ✅ |
| 节点右键菜单 | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| Edge 右键菜单 | ✅(PR#33391) | ✅ | ✅ | ❌ | ❌ | ✅ |
| Delete 键删除 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ctrl+D 复制 | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Ctrl+Z 撤销 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| 连线 "+" 按钮 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
2.2 画布布局对比
| 产品 | 左侧面板 | 右侧面板 | 工具栏 | 布局风格 |
|---|---|---|---|---|
| Dify | 节点选择(可折叠) | 检查器(选中后出现) | 顶部薄栏 | 左-中-右 |
| n8n | 节点库 | 编辑面板 | 顶部 | 左-中-右 |
| Make | 模块库(浮层) | 配置面板 | 顶部 | 中-右(浮层) |
| Zapier | 无(步骤式) | 弹窗配置 | 顶部 | 步骤向导 |
| Coze | 节点/插件/知识库 | 配置区(底部弹出) | 顶部 | 左-中-右 |
| 本产品 | 节点库+工具区 | 检查器面板 | 顶部 48px | 左-中-右 |
3. 节点体系设计
3.1 三节点 vs 五节点体系
行业共识:主流产品均采用 3~4 种核心节点类型。 Dify: Start → LLM/Tool → End n8n: Trigger → Action → No explicit end Make: Trigger → Module → No explicit end Zapier: Trigger → Action/Search → No explicit end Coze: Start → LLM/Plugin/Knowledge → End
本产品的演进:
- 旧(SY18 模型):5 种节点(input / connector / action / result / permission)
- 新(Zapier/Dify 对齐):3 种节点(start / action / end)
- start(开始节点):取代旧的 input + connector
- action(动作节点):保留旧的 action
- end(结束节点):取代旧的 result + permission
3.2 子类型(SubType)架构
三节点通过 子类型(SubType) 实现具体的行为分化,避免了节点类型爆炸:
start (开始节点)
├── schedule (⏰ 定时触发)
├── file (📎 文件触发)
├── connector (🔌 连接器触发)
└── manual (✍️ 手动触发)
action (动作节点)
├── ai (🤖 AI 处理)
├── code (💻 代码处理)
├── condition (🔀 条件分支)
└── connector (🔌 连接器操作)
end (结束节点)
├── data (📊 返回数据)
├── file (📁 文件输出)
└── connector (🔌 连接器输出)
核心设计原则:
- 每种节点只有一个 子类型下拉选择,用户在检查器中切换
- subType 改变时自动清除不兼容的配置(如从
connector切到ai时清除connectorKey) - 子类型的标签包含 emoji 前缀,便于视觉识别(参考 Dify 的节点 icon + label)
3.3 连接器作为被引用资源
关键架构决策:连接器不是画布上的独立节点类型。
在旧的 SY18 模型中,"信源"被理解为连接器。经过对 Zapier/Make.com/Coze/Dify 的调研发现:
| 产品 | 连接器(Connector)的角色 |
|---|---|
| Zapier | Trigger 和 Action 通过下拉选择 APP + 事件/操作,APP 就是连接器 |
| Make | Module 可以选择不同的 Service,Service 就是连接器 |
| Dify | 工具(Tools)是独立节点,但连接器/数据源是 LLM 或知识库的属性 |
| Coze | 插件(Plugin)是独立节点,连接器是 Bot 的配置属性 |
本产品的方案:
- 连接器不是画布节点,而是 start/action/end 节点通过 subType='connector' 引用的资源
- 连接器列表在 工具区 中管理,由工具栏
☑ 🔌 连接器按钮控制显示/隐藏 - 用户可以从工具区 点击添加 一个 start 节点并自动绑定到选中的连接器
- 在检查器中,当 subType='connector' 时,显示连接器绑定下拉
4. 画布交互模型
4.1 拖入创建(Drag & Drop)
Dify 标准流程:
- 从左侧节点列表拖出一个节点类型
- 拖到画布上释放
- 节点出现在释放位置,自动选中并打开右侧配置面板
本产品实现:
<!-- 工具箱节点 -->
<div
v-for="item in toolboxItems"
draggable="true"
@dragstart="handleToolboxDragStart($event, item.key)"
@dragend="handleToolboxDragEnd"
>
...
</div>
<!-- 画布接收 drop -->
<div
class="canvas-shell"
@dragover.prevent="handleCanvasDragOver"
@drop.prevent="handleCanvasDrop"
@dragleave="handleCanvasDragLeave"
>
...
</div>
拖放数据流:
dragstart → setData('application/dw-node-kind', kind)
dragover → 显示 drop 提示(drag-over class)
drop → screenToFlowCoordinate → createCanvasNode
dragend → 清理状态
4.2 右键菜单体系
Dify 标准(PR #34138 + PR #33391):
- 节点右键菜单:编辑节点 / 复制节点 / 删除节点 / 断开所有连线
- 新版本还支持多选节点时的对齐操作
- Edge 右键菜单:删除连线(PR #33391, 2026-03 合并)
- 右键点击连线弹出删除选项
- 菜单互斥:同一时间只有一个菜单可见
- 画布空白右键菜单:粘贴 / 适配视图 / 重置
本产品实现:
节点右键菜单:
<div v-if="contextMenu.visible" class="node-context-menu"
:style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }" @click.stop>
<div class="context-menu-item" @click="editNodeFromContext">✏️ 编辑节点</div>
<div class="context-menu-item" @click="duplicateNodeFromContext">📋 复制节点</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" @click="deleteNodeFromContext">🗑️ 删除节点</div>
<div class="context-menu-item" @click="disconnectNodeFromContext">🔗 断开所有连线</div>
</div>
<!-- Edge 右键菜单 -->
<div v-if="edgeContextMenu.visible" class="node-context-menu edge-context-menu"
:style="{ left: edgeContextMenu.x + 'px', top: edgeContextMenu.y + 'px' }" @click.stop>
<div class="context-menu-item" @click="deleteEdgeFromContext">🗑️ 删除连线</div>
</div>
菜单互斥逻辑:
function handleEdgeContextMenu({ edge, event }) {
event.preventDefault()
closeNodeContextMenu() // 关闭节点菜单
closeEdgeContextMenu() // 关闭已有边菜单
edgeContextMenu.edgeId = edge.id
edgeContextMenu.x = event.clientX
edgeContextMenu.y = event.clientY
edgeContextMenu.visible = true
}
function handlePaneClick() {
selectedNodeId.value = ''
closeNodeContextMenu()
closeEdgeContextMenu()
}
4.3 键盘快捷键体系
Dify 快捷键标准:
| 快捷键 | 功能 |
|---|---|
| Delete | 删除选中的节点或边 |
| Backspace | 删除选中节点或边(有争议,用户易误触) |
| Ctrl+D | 复制选中节点 |
| Ctrl+Z | 撤销 |
| Ctrl+Shift+Z / Ctrl+Y | 重做 |
| Shift + 点击 | 多选节点 |
| Arrow keys | 移动选中节点 |
| Ctrl+A | 全选 |
本产品实现:
function handleKeyDown(event) {
const tag = event.target.tagName
// 输入框中不触发
if (tag === 'INPUT' || tag === 'TEXTAREA' || event.target.isContentEditable) return
if (event.key === 'Delete' || event.key === 'Backspace') {
// 1. 先关菜单(如果有)
if (contextMenu.visible || edgeContextMenu.visible) {
closeNodeContextMenu()
closeEdgeContextMenu()
event.preventDefault()
return
}
// 2. 删选中的边
const selectedEdges = edges.value.filter(e => e.selected)
if (selectedEdges.length > 0) {
edges.value = edges.value.filter(e => !e.selected)
event.preventDefault()
return
}
// 3. 删选中的节点
const selectedNodes = nodes.value.filter(n => n.selected)
if (selectedNodes.length > 0) {
const selectedIds = new Set(selectedNodes.map(n => n.id))
nodes.value = nodes.value.filter(n => !selectedIds.has(n.id))
edges.value = edges.value.filter(e => !selectedIds.has(e.source) && !selectedIds.has(e.target))
event.preventDefault()
}
}
}
4.4 选中与视觉反馈
节点选中状态:
- VueFlow 原生支持
node.selected属性 - 点击节点 → 节点获得
.selectedclass → 边框变色 + 阴影增强 - 点击画布空白 → 取消全部选中
本产品的颜色体系:
| 节点类型 | 边框色 | 选中色 |
|---|---|---|
| start | #409eff (蓝) |
实线蓝色 + 蓝光阴影 |
| action | #e6a23c (橙) |
实线橙色 + 橙光阴影 |
| end | #67c23a (绿) |
实线绿色 + 绿光阴影 |
4.5 Edge 交互(连线)
Dify 标准:
- 连接操作:从一个节点的右侧 handle 拖到另一个节点的左侧 handle
- 选中:点击连线 → 连线高亮(变蓝变粗)
- 右键菜单:右键点击连线 → 弹出删除选项
- Delete 键删除:选中连线后按 Delete 键
- Hover "+" 按钮:鼠标悬停在连线上 → 中点出现 "+" 按钮 → 点击后插入一个新节点(串行)
- 断开连接:从 handle 拖出新连线覆盖已有连线,自动替换
本产品实现:
Edge 视觉反馈(CSS):
/* 点击区域加宽 */
:deep(.vue-flow__edge-path) {
stroke-linecap: round;
transition: stroke 0.15s, stroke-width 0.15s;
}
/* hover 高亮 */
:deep(.vue-flow__edge:hover .vue-flow__edge-path) {
stroke: #409eff;
stroke-width: 2.5;
}
/* 选中高亮 */
:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
stroke: #409eff;
stroke-width: 3;
}
Handle hover:
:deep(.vue-flow__handle) {
transition: transform 0.12s, background 0.12s;
}
:deep(.vue-flow__handle:hover) {
transform: scale(1.3);
background: #409eff;
}
5. 布局与导航
5.1 整体布局结构
┌─────────────────────────────────────────────────────────────┐
│ [头部 Header · 48px] │
├────────┬────────────────────────────────────┬───────────────┤
│ 左侧 │ 画布区域 │ 右侧检查器 │
│ 节点库 │ Canvas (VueFlow) │ Inspector │
│ +工具区 │ │ (选中后出现) │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ S │──→│ A │──→│ E │ │ │
│ │ └────┘ └────┘ └────┘ │ │
│ │ │ │
├────────┴────────────────────────────────────┴───────────────┤
│ [工具栏] ← ☑ 🔌 连接器 | ⚙ 设置 | [保存] │
└─────────────────────────────────────────────────────────────┘
5.2 左侧节点库(Toolbox)
| 区域 | 内容 | 可折叠 |
|---|---|---|
| 头部 | "节点库" + ◀ 折叠按钮 | ✅ |
| 节点列表 | 开始节点/动作节点/结束节点(可拖拽) | 跟随折叠 |
| 分隔线 | - | - |
| 工具区标题 | "工具区" | - |
| 连接器工具 | 连接器列表(由工具栏按钮控制可见性) | ✅(点击文字折叠) |
展开状态宽度:200px 折叠状态宽度:52px(只显示图标)
5.3 右侧检查器(Inspector)
| 区域 | 内容 |
|---|---|
| 头部 | "节点检查器" |
| 当前节点 | 显示节点标题(只读) |
| 节点类型 | 显示类型标签(只读) |
| 子类型选择 | el-select 下拉(start: 定时/文件/连接器/手动; action: AI/代码/条件/连接器; end: 数据/文件/连接器) |
| 连接器绑定 | 仅 subType='connector' 时显示,el-select 选择连接器 |
| 节点标题 | el-input 可编辑 |
| 节点说明 | el-input textarea 可编辑 |
| 节点位置 | X/Y 坐标(只读) |
| 上下游 | 上游节点 / 下游节点(只读) |
6. 技术实现(VueFlow)
6.1 核心事件
| VueFlow 事件 | 用途 | 当前状态 |
|---|---|---|
@node-click |
点击节点 → 选中 + 显示检查器 | ✅ |
@node-context-menu |
右键节点 → 弹出菜单 | ✅ |
@edge-click |
点击连线 → 选中高亮 | ✅ |
@edge-context-menu |
右键连线 → 删除菜单 | ✅ |
@connect |
建立连线 | ✅ |
@pane-click |
点击画布空白 → 取消选中 + 关菜单 | ✅ |
@pane-ready |
画布初始化 → 绑定键盘事件 | ✅ |
@nodes-change |
节点变化 | ❌(未用) |
@edges-change |
边变化 | ❌(未用) |
@node-drag-stop |
拖拽停止 | ❌(未用) |
6.2 自定义节点
// 节点定义
const newNode = {
id: `${kind}-${nodeCounter}`,
type: 'dw', // 自定义节点类型,对应 <template #node-dw>
position: { x, y },
draggable: true,
connectable: true,
selectable: true,
data: {
kind, // 'start' | 'action' | 'end'
typeLabel, // '▶ 开始' | '⚡ 动作' | '📤 结束'
subType, // 子类型,默认取第一个
title, // 节点标题
detail, // 节点说明
connectorKey, // 仅 subType='connector' 时有值
},
}
<!-- 自定义节点渲染模板 -->
<template #node-dw="{ data, selected }">
<div class="dw-node" :class="[data.kind, { selected }]">
<Handle type="target" :position="Position.Left" />
<div class="dw-node-type">{{ data.typeLabel }}</div>
<div v-if="data.subType" class="dw-node-subtype">
{{ nodeKindConfig[data.kind]?.subTypes?.find(s => s.value === data.subType)?.label || '' }}
</div>
<div class="dw-node-title">{{ data.title }}</div>
<div class="dw-node-detail">{{ data.detail }}</div>
<Handle type="source" :position="Position.Right" />
</div>
</template>
6.3 自定义边
目前使用 VueFlow 内置边类型:
type: 'smoothstep'— 圆角折线markerEnd: MarkerType.ArrowClosed— 终点箭头
待实现的自定义边功能:
- 边中点悬停 "+" 按钮(需自定义
edgeTypes) - 边标签显示(显示连线条件或数据映射)
6.4 CSS 主题与样式
必须导入的样式:
import '@vue-flow/core/dist/style.css' // 基础样式
import '@vue-flow/core/dist/theme-default.css' // 可选默认主题
关键 CSS 变量覆盖:
:root {
--vf-node-bg: #fff;
--vf-node-text: #1f2d3d;
--vf-connection-path: #b1b1b7;
--vf-handle: #555;
}
自定义节点类名:
| 类名 | 用途 |
|---|---|
.dw-node |
所有自定义节点 |
.dw-node.start |
开始节点(蓝色边框) |
.dw-node.action |
动作节点(橙色边框) |
.dw-node.end |
结束节点(绿色边框) |
.dw-node.selected |
选中态(边框变色 + 阴影) |
7. 当前实现状态
7.1 已完成功能
| 功能 | 状态 | 实现位置 |
|---|---|---|
| 3 节点体系(start/action/end) | ✅ | toolboxItems + nodeKindConfig |
| 子类型下拉选择 | ✅ | 检查器面板 el-select |
| 连接器绑定(subType=connector) | ✅ | 检查器面板条件渲染 |
| 拖入创建(drag & drop) | ✅ | handleToolboxDragStart → handleCanvasDrop |
| 节点右键菜单 | ✅ | handleNodeContextMenu → 4 个菜单项 |
| Edge 右键菜单 | ✅ | handleEdgeContextMenu → 删除连线 |
| Delete 键删除 | ✅ | handleKeyDown(全局 document 监听) |
| Edge hover 高亮 | ✅ | CSS :deep(.vue-flow__edge:hover) |
| Edge 选中高亮 | ✅ | CSS :deep(.vue-flow__edge.selected) |
| Handle hover 放大 | ✅ | CSS :deep(.vue-flow__handle:hover) |
| 工具栏连接器开关 | ✅ | connectorToolEnabled + ☑/☐ 按钮 |
| 工具区折叠 | ✅ | connectorListCollapsed + 点击"连接器"文字 |
| 连接器创建弹窗 | ✅ | connectorDialogVisible + createConnectorFromForm |
| 右键菜单互斥 | ✅ | closeNodeContextMenu() / closeEdgeContextMenu() |
| 键盘监听生命周期 | ✅ | onMounted 添加 / onUnmounted 移除 |
| 输入保护(不误删) | ✅ | tagName === 'INPUT' 等判断 |
7.2 当前代码架构
StudioPage.vue
├── Template
│ ├── 工具栏 (.canvas-toolbar)
│ │ ├── 返回按钮 + 标题 + 节点/边计数
│ │ └── ☑ 连接器 + ⚙ 设置 + 保存
│ ├── 新手引导 (无模式时)
│ ├── 模式选择弹窗 (模板/复制)
│ ├── 设置弹窗
│ ├── 连接器创建弹窗
│ ├── 主布局 (.studio-grid)
│ │ ├── 左侧节点库+工具区 (.toolbox-panel)
│ │ ├── 中间画布 (.canvas-shell > VueFlow)
│ │ └── 右侧检查器 (.inspector-panel)
│ ├── 节点右键菜单 (.node-context-menu)
│ └── Edge 右键菜单 (.edge-context-menu)
├── Script
│ ├── 状态定义 (ref/reactive)
│ ├── 计算属性 (computed)
│ ├── 模板/配置数据 (templateCards, toolboxItems, nodeKindConfig)
│ ├── 业务函数 (createDraft, rebootCanvas, saveDw, ...)
│ ├── 交互函数 (handleNodeClick, handleNodeContextMenu, handleKeyDown, ...)
│ └── 生命周期 (onMounted, onUnmounted, watch)
└── Style (scoped)
├── 工具栏 (.canvas-toolbar)
├── 新手引导 (.canvas-landing)
├── 主布局 (.studio-grid)
├── 节点库 (.toolbox-panel / .toolbox-item)
├── 工具区 (.toolbox-tool-area)
├── 画布 (.canvas-shell / .dw-flow)
├── DW 节点 (.dw-node / .dw-node.start/.action/.end)
├── 检查器 (.inspector-panel)
├── 弹窗公用 (.dialog-template-grid)
├── 连接器 (.toolbox-connector-item)
├── 右键菜单 (.node-context-menu)
├── Edge 交互 (vue-flow__edge hover/selected)
└── 响应式 (@media)
8. 待完成项
8.1 高优先级
| 功能 | 说明 | Dify 参考 |
|---|---|---|
| Ctrl+D 复制节点 | 选中节点后按 Ctrl+D 快速复制 | ✅ |
| Ctrl+Z 撤销/重做 | 历史记录栈,支持撤销和重做操作 | ✅ |
| Edge "+" 按钮 | hover 在连线中点显示 "+",点击后插入新节点(串行) | ✅ (最新版本) |
| 编译面板清理 | compileCanvas() 中仍有 input/connector/result/permission 旧 kind 引用 |
- |
8.2 中优先级
| 功能 | 说明 |
|---|---|
| 画布右键菜单 | 右键点击画布空白 → 粘贴/适配视图/重置 |
| 多选操作 | Shift+点击 多选,批量删除/复制/对齐 |
| 对齐辅助线 | 拖拽节点时的参考线 |
| MiniMap | 缩略图导航 |
| Edge 标签 | 在连线上显示条件标签(如"是/否") |
8.3 低优先级
| 功能 | 说明 |
|---|---|
| 自动保存 | 画布变化后自动保存草稿 |
| 全屏模式 | 画布全屏编辑 |
| 节点注释/便签 | 在画布上添加文字注释 |
| 模板片段 | 选中部分节点另存为模板片段 |
| 导入/导出 | JSON 导入导出画布定义 |
9. 参考资料
9.1 Dify 相关
| 链接 | 内容 |
|---|---|
| Dify Edge Context Menu PR #33391 | 2026-03 合并的 Edge 右键菜单实现 |
| Dify Multi Node Context Menu PR #34138 | 2026-03 合并的多选节点菜单 |
| Dify Backspace Issue #23354 | Backspace 误删节点问题讨论 |
| Dify Backspace Discussion #20140 | 用户要求移除 Backspace 快捷键 |
9.2 VueFlow 相关
| 链接 | 内容 |
|---|---|
| VueFlow 官方文档 | 核心 API、自定义节点/边、主题 |
| VueFlow Edge Guide | 边的增删改查 |
| VueFlow Theming Guide | CSS 变量、自定义主题 |
9.3 本产品内部文档
| 文档 | 内容 |
|---|---|
| SY18 - 专员最小定义模型 | 信源/动作/结果/权限模型 |
| SY17 - UI 线框图 | 工作台 UI 框架 |
| DR03 - 交互规范 | 全局交互规范 |
文档维护者:AI 工程团队 下次评审:2026-08-24