docs(frontend): 添加自由布局编辑器的演示文档和最佳实践指南

新增 flow-free-layout-demo.md 和 flow-free-layout-simple-demo.md 文档
包含编辑器架构设计、核心功能、安装指南和示例代码
This commit is contained in:
2025-08-30 00:19:26 +08:00
parent 2b1308956a
commit 9da3978f91
4 changed files with 1113 additions and 0 deletions

View File

View File

@ -0,0 +1,701 @@
# 基础用法
import { FreeLayoutSimplePreview } from '../../../../components';
<FreeLayoutSimplePreview />
## 功能介绍
Free Layout 是 Flowgram.ai 提供的自由布局编辑器组件,允许用户创建和编辑流程图、工作流和各种节点连接图表。核心功能包括:
* 节点自由拖拽与定位
* 节点连接与边缘管理
* 可配置的节点注册与自定义渲染
* 内置撤销/重做历史记录
* 支持插件扩展(如缩略图、自动对齐等)
## 从零构建自由布局编辑器
本节将带你从零开始构建一个自由布局编辑器应用,完整演示如何使用 @flowgram.ai/free-layout-editor 包构建一个可交互的流程编辑器。
### 1. 环境准备
首先,我们需要创建一个新的项目:
```bash
# 使用脚手架快速创建项目
npx @flowgram.ai/create-app@latest free-layout-simple
# 进入项目目录
cd free-layout-simple
# 安装依赖
npm install
```
### 2. 项目结构
创建完成后,项目结构如下:
```
free-layout-simple/
├── src/
│ ├── components/ # 组件目录
│ │ ├── node-add-panel.tsx # 节点添加面板
│ │ ├── tools.tsx # 工具栏组件
│ │ └── minimap.tsx # 缩略图组件
│ ├── hooks/
│ │ └── use-editor-props.tsx # 编辑器配置
│ ├── initial-data.ts # 初始数据定义
│ ├── node-registries.ts # 节点类型注册
│ ├── editor.tsx # 编辑器主组件
│ ├── app.tsx # 应用入口
│ ├── index.tsx # 渲染入口
│ └── index.css # 样式文件
├── package.json
└── ...其他配置文件
```
### 3. 开发流程
#### 步骤一:定义初始数据
首先,我们需要定义画布的初始数据结构,包括节点和连线:
```tsx
// src/initial-data.ts
import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
export const initialData: WorkflowJSON = {
nodes: [
{
id: 'start_0',
type: 'start',
meta: {
position: { x: 0, y: 0 },
},
data: {
title: '开始节点',
content: '这是开始节点'
},
},
{
id: 'node_0',
type: 'custom',
meta: {
position: { x: 400, y: 0 },
},
data: {
title: '自定义节点',
content: '这是自定义节点'
},
},
{
id: 'end_0',
type: 'end',
meta: {
position: { x: 800, y: 0 },
},
data: {
title: '结束节点',
content: '这是结束节点'
},
},
],
edges: [
{
sourceNodeID: 'start_0',
targetNodeID: 'node_0',
},
{
sourceNodeID: 'node_0',
targetNodeID: 'end_0',
},
],
};
```
#### 步骤二:注册节点类型
接下来,我们需要定义不同类型节点的行为和外观:
```tsx
// src/node-registries.ts
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
/**
* 你可以自定义节点的注册器
*/
export const nodeRegistries: WorkflowNodeRegistry[] = [
{
type: 'start',
meta: {
isStart: true, // 开始节点标记
deleteDisable: true, // 开始节点不能被删除
copyDisable: true, // 开始节点不能被 copy
defaultPorts: [{ type: 'output' }], // 定义 input 和 output 端口,开始节点只有 output 端口
},
},
{
type: 'end',
meta: {
deleteDisable: true,
copyDisable: true,
defaultPorts: [{ type: 'input' }], // 结束节点只有 input 端口
},
},
{
type: 'custom',
meta: {},
defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
},
];
```
#### 步骤三:创建编辑器配置
使用 React hook 封装编辑器配置:
```tsx
// src/hooks/use-editor-props.tsx
import { useMemo } from 'react';
import {
FreeLayoutProps,
WorkflowNodeProps,
WorkflowNodeRenderer,
Field,
useNodeRender,
} from '@flowgram.ai/free-layout-editor';
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
import { nodeRegistries } from '../node-registries';
import { initialData } from '../initial-data';
export const useEditorProps = () =>
useMemo<FreeLayoutProps>(
() => ({
// 启用背景网格
background: true,
// 非只读模式
readonly: false,
// 初始数据
initialData,
// 节点类型注册
nodeRegistries,
// 默认节点注册
getNodeDefaultRegistry(type) {
return {
type,
meta: {
defaultExpanded: true,
},
formMeta: {
// 节点表单渲染
render: () => (
<>
<Field<string> name="title">
{({ field }) => <div className="demo-free-node-title">{field.value}</div>}
</Field>
<div className="demo-free-node-content">
<Field<string> name="content">
<input />
</Field>
</div>
</>
),
},
};
},
// 节点渲染
materials: {
renderDefaultNode: (props: WorkflowNodeProps) => {
const { form } = useNodeRender();
return (
<WorkflowNodeRenderer className="demo-free-node" node={props.node}>
{form?.render()}
</WorkflowNodeRenderer>
);
},
},
// 内容变更回调
onContentChange(ctx, event) {
console.log('数据变更: ', event, ctx.document.toJSON());
},
// 启用节点表单引擎
nodeEngine: {
enable: true,
},
// 启用历史记录
history: {
enable: true,
enableChangeNode: true, // 监听节点引擎数据变化
},
// 初始化回调
onInit: (ctx) => {},
// 渲染完成回调
onAllLayersRendered(ctx) {
ctx.document.fitView(false); // 适应视图
},
// 销毁回调
onDispose() {
console.log('编辑器已销毁');
},
// 插件配置
plugins: () => [
// 缩略图插件
createMinimapPlugin({
disableLayer: true,
canvasStyle: {
canvasWidth: 182,
canvasHeight: 102,
canvasPadding: 50,
canvasBackground: 'rgba(245, 245, 245, 1)',
canvasBorderRadius: 10,
viewportBackground: 'rgba(235, 235, 235, 1)',
viewportBorderRadius: 4,
viewportBorderColor: 'rgba(201, 201, 201, 1)',
viewportBorderWidth: 1,
viewportBorderDashLength: 2,
nodeColor: 'rgba(255, 255, 255, 1)',
nodeBorderRadius: 2,
nodeBorderWidth: 0.145,
nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
overlayColor: 'rgba(255, 255, 255, 0)',
},
inactiveDebounceTime: 1,
}),
// 自动对齐插件
createFreeSnapPlugin({
edgeColor: '#00B2B2',
alignColor: '#00B2B2',
edgeLineWidth: 1,
alignLineWidth: 1,
alignCrossWidth: 8,
}),
],
}),
[]
);
```
#### 步骤四:创建节点添加面板
```tsx
// src/components/node-add-panel.tsx
import React from 'react';
import { WorkflowDragService, useService } from '@flowgram.ai/free-layout-editor';
const nodeTypes = ['自定义节点1', '自定义节点2'];
export const NodeAddPanel: React.FC = () => {
const dragService = useService<WorkflowDragService>(WorkflowDragService);
return (
<div className="demo-free-sidebar">
{nodeTypes.map(nodeType => (
<div
key={nodeType}
className="demo-free-card"
onMouseDown={e => dragService.startDragCard(nodeType, e, {
data: {
title: nodeType,
content: '拖拽创建的节点'
}
})}
>
{nodeType}
</div>
))}
</div>
);
};
```
#### 步骤五:创建工具栏和缩略图
```tsx
// src/components/tools.tsx
import React from 'react';
import { useEffect, useState } from 'react';
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
export const Tools: React.FC = () => {
const { history } = useClientContext();
const tools = usePlaygroundTools();
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
useEffect(() => {
const disposable = history.undoRedoService.onChange(() => {
setCanUndo(history.canUndo());
setCanRedo(history.canRedo());
});
return () => disposable.dispose();
}, [history]);
return (
<div
style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}
>
<button onClick={() => tools.zoomin()}>ZoomIn</button>
<button onClick={() => tools.zoomout()}>ZoomOut</button>
<button onClick={() => tools.fitView()}>Fitview</button>
<button onClick={() => tools.autoLayout()}>AutoLayout</button>
<button onClick={() => history.undo()} disabled={!canUndo}>
Undo
</button>
<button onClick={() => history.redo()} disabled={!canRedo}>
Redo
</button>
<span>{Math.floor(tools.zoom * 100)}%</span>
</div>
);
};
// src/components/minimap.tsx
import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
import { useService } from '@flowgram.ai/free-layout-editor';
export const Minimap = () => {
const minimapService = useService(FlowMinimapService);
return (
<div
style={{
position: 'absolute',
left: 226,
bottom: 51,
zIndex: 100,
width: 198,
}}
>
<MinimapRender
service={minimapService}
containerStyles={{
pointerEvents: 'auto',
position: 'relative',
top: 'unset',
right: 'unset',
bottom: 'unset',
left: 'unset',
}}
inactiveStyle={{
opacity: 1,
scale: 1,
translateX: 0,
translateY: 0,
}}
/>
</div>
);
};
```
#### 步骤六:组装编辑器主组件
```tsx
// src/editor.tsx
import { EditorRenderer, FreeLayoutEditorProvider } from '@flowgram.ai/free-layout-editor';
import { useEditorProps } from './hooks/use-editor-props';
import { Tools } from './components/tools';
import { NodeAddPanel } from './components/node-add-panel';
import { Minimap } from './components/minimap';
import '@flowgram.ai/free-layout-editor/index.css';
import './index.css';
export const Editor = () => {
const editorProps = useEditorProps();
return (
<FreeLayoutEditorProvider {...editorProps}>
<div className="demo-free-container">
<div className="demo-free-layout">
<NodeAddPanel />
<EditorRenderer className="demo-free-editor" />
</div>
<Tools />
<Minimap />
</div>
</FreeLayoutEditorProvider>
);
};
```
#### 步骤七:创建应用入口
```tsx
// src/app.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Editor } from './editor';
ReactDOM.render(<Editor />, document.getElementById('root'))
```
#### 步骤八:添加样式
```css
/* src/index.css */
.demo-free-node {
display: flex;
min-width: 300px;
min-height: 100px;
flex-direction: column;
align-items: flex-start;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08));
background: #fff;
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
}
.demo-free-node-title {
background-color: #93bfe2;
width: 100%;
border-radius: 8px 8px 0 0;
padding: 4px 12px;
}
.demo-free-node-content {
padding: 4px 12px;
flex-grow: 1;
width: 100%;
}
.demo-free-node::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
background-color: white;
border-radius: 7px;
}
.demo-free-node:hover:before {
-webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
}
.demo-free-node.activated:before,
.demo-free-node.selected:before {
outline: 2px solid var(--light-usage-primary-color-primary, #4d53e8);
-webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
}
.demo-free-sidebar {
height: 100%;
overflow-y: auto;
padding: 12px 16px 0;
box-sizing: border-box;
background: #f7f7fa;
border-right: 1px solid rgba(29, 28, 35, 0.08);
}
.demo-free-right-top-panel {
position: fixed;
right: 10px;
top: 70px;
width: 300px;
z-index: 999;
}
.demo-free-card {
width: 140px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 6px 8px 0 rgba(28, 31, 35, 0.03);
cursor: -webkit-grab;
cursor: grab;
line-height: 16px;
margin-bottom: 12px;
overflow: hidden;
padding: 16px;
position: relative;
color: black;
}
.demo-free-layout {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.demo-free-editor {
flex-grow: 1;
position: relative;
height: 100%;
}
.demo-free-container {
position: absolute;
left: 0;
top: 0;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
}
```
### 4. 运行项目
完成上述步骤后,你可以运行项目查看效果:
```bash
npm run dev
```
项目将在本地启动,通常访问 http://localhost:3000 即可看到效果。
## 核心概念
### 1. 数据结构
Free Layout 使用标准化的数据结构来描述节点和连接:
```tsx
// 工作流数据结构
const initialData: WorkflowJSON = {
// 节点定义
nodes: [
{
id: 'start_0', // 节点唯一ID
type: 'start', // 节点类型(对应 nodeRegistries 中的注册)
meta: {
position: { x: 0, y: 0 }, // 节点位置
},
data: {
title: 'Start', // 节点数据(可自定义)
content: 'Start content'
},
},
// 更多节点...
],
// 连线定义
edges: [
{
sourceNodeID: 'start_0', // 源节点ID
targetNodeID: 'node_0', // 目标节点ID
},
// 更多连线...
],
};
```
### 2. 节点注册
使用 `nodeRegistries` 定义不同类型节点的行为和外观:
```tsx
// 节点注册
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
export const nodeRegistries: WorkflowNodeRegistry[] = [
// 开始节点定义
{
type: 'start',
meta: {
isStart: true, // Mark as start
deleteDisable: true, // The start node cannot be deleted
copyDisable: true, // The start node cannot be copied
defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
},
},
// 更多节点类型...
];
```
### 3. 编辑器组件
```tsx
// 核心编辑器容器与渲染器
import {
FreeLayoutEditorProvider,
EditorRenderer
} from '@flowgram.ai/free-layout-editor';
// 编辑器配置示例
const editorProps = {
background: true, // 启用背景网格
readonly: false, // 非只读模式,允许编辑
initialData: {...}, // 初始化数据:节点和边的定义
nodeRegistries: [...], // 节点类型注册
nodeEngine: {
enable: true, // 启用节点表单引擎
},
history: {
enable: true, // 启用历史记录
enableChangeNode: true, // 监听节点数据变化
}
};
// 完整编辑器渲染
<FreeLayoutEditorProvider {...editorProps}>
<div className="container">
<NodeAddPanel /> {/* 节点添加面板 */}
<EditorRenderer /> {/* 核心编辑器渲染区域 */}
<Tools /> {/* 工具栏 */}
<Minimap /> {/* 缩略图 */}
</div>
</FreeLayoutEditorProvider>
```
### 4. 核心钩子函数
在组件中可以使用多种钩子函数获取和操作编辑器:
```tsx
// 获取拖拽服务
const dragService = useService<WorkflowDragService>(WorkflowDragService);
// 开始拖拽节点
dragService.startDragCard('nodeType', event, { data: {...} });
// 获取编辑器上下文
const { document, playground } = useClientContext();
// 操作画布
document.fitView(); // 适应视图
playground.config.zoomin(); // 缩放画布
document.fromJSON(newData); // 更新数据
```
### 5. 插件扩展
Free Layout 支持通过插件机制扩展功能:
```tsx
plugins: () => [
// 缩略图插件
createMinimapPlugin({
canvasStyle: {
canvasWidth: 180,
canvasHeight: 100,
canvasBackground: 'rgba(245, 245, 245, 1)',
}
}),
// 自动对齐插件
createFreeSnapPlugin({
edgeColor: '#00B2B2', // 对齐线颜色
alignColor: '#00B2B2', // 辅助线颜色
edgeLineWidth: 1, // 线宽
}),
],
```
## 安装
```bash
npx @flowgram.ai/create-app@latest free-layout-simple
```
## 源码
https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout-simple

View File

View File

@ -0,0 +1,412 @@
# 最佳实践
import { FreeFeatureOverview } from '../../../../components';
<FreeFeatureOverview />
## 安装
```shell
npx @flowgram.ai/create-app@latest free-layout
```
## 源码
https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout
## 项目概览
### 核心技术栈
* **前端框架**: React 18 + TypeScript
* **构建工具**: Rsbuild (基于 Rspack 的现代构建工具)
* **样式方案**: Less + Styled Components + CSS Variables
* **UI 组件库**: Semi Design (@douyinfe/semi-ui)
* **状态管理**: 基于 Flowgram 自研的编辑器框架
* **依赖注入**: Inversify
### 核心依赖包
* **@flowgram.ai/free-layout-editor**: 自由布局编辑器核心依赖
* **@flowgram.ai/free-snap-plugin**: 自动对齐及辅助线插件
* **@flowgram.ai/free-lines-plugin**: 连线渲染插件
* **@flowgram.ai/free-node-panel-plugin**: 节点添加面板渲染插件
* **@flowgram.ai/minimap-plugin**: 缩略图插件
* **@flowgram.ai/free-container-plugin**: 子画布插件
* **@flowgram.ai/free-group-plugin**: 分组插件
* **@flowgram.ai/form-materials**: 表单物料
* **@flowgram.ai/runtime-interface**: 运行时接口
* **@flowgram.ai/runtime-js**: js 运行时模块
## 代码说明
### 目录结构
```
src/
├── app.tsx # 应用入口文件
├── editor.tsx # 编辑器主组件
├── initial-data.ts # 初始化数据配置
├── assets/ # 静态资源
├── components/ # 组件库
│ ├── index.ts
│ ├── add-node/ # 添加节点组件
│ ├── base-node/ # 基础节点组件
│ ├── comment/ # 注释组件
│ ├── group/ # 分组组件
│ ├── line-add-button/ # 连线添加按钮
│ ├── node-menu/ # 节点菜单
│ ├── node-panel/ # 节点添加面板
│ ├── selector-box-popover/ # 选择框弹窗
│ ├── sidebar/ # 侧边栏
│ ├── testrun/ # 测试运行组件
│ │ ├── hooks/ # 测试运行钩子
│ │ ├── node-status-bar/ # 节点状态栏
│ │ ├── testrun-button/ # 测试运行按钮
│ │ ├── testrun-form/ # 测试运行表单
│ │ ├── testrun-json-input/ # JSON输入组件
│ │ └── testrun-panel/ # 测试运行面板
│ └── tools/ # 工具组件
├── context/ # React Context
│ ├── node-render-context.ts # 当前渲染节点 Context
│ ├── sidebar-context # 侧边栏 Context
├── form-components/ # 表单组件库
│ ├── form-content/ # 表单内容
│ ├── form-header/ # 表单头部
│ ├── form-inputs/ # 表单输入
│ └── form-item/ # 表单项
│ └── feedback.tsx # 表单校验错误渲染
├── hooks/
│ ├── index.ts
│ ├── use-editor-props.tsx # 编辑器属性钩子
│ ├── use-is-sidebar.ts # 侧边栏状态钩子
│ ├── use-node-render-context.ts # 节点渲染上下文钩子
│ └── use-port-click.ts # 端口点击钩子
├── nodes/ # 节点定义
│ ├── index.ts
│ ├── constants.ts # 节点常量定义
│ ├── default-form-meta.ts # 默认表单元数据
│ ├── block-end/ # 块结束节点
│ ├── block-start/ # 块开始节点
│ ├── break/ # 中断节点
│ ├── code/ # 代码节点
│ ├── comment/ # 注释节点
│ ├── condition/ # 条件节点
│ ├── continue/ # 继续节点
│ ├── end/ # 结束节点
│ ├── group/ # 分组节点
│ ├── http/ # HTTP节点
│ ├── llm/ # LLM节点
│ ├── loop/ # 循环节点
│ ├── start/ # 开始节点
│ └── variable/ # 变量节点
├── plugins/ # 插件系统
│ ├── index.ts
│ ├── context-menu-plugin/ # 右键菜单插件
│ ├── runtime-plugin/ # 运行时插件
│ │ ├── client/ # 客户端
│ │ │ ├── browser-client/ # 浏览器客户端
│ │ │ └── server-client/ # 服务器客户端
│ │ └── runtime-service/ # 运行时服务
│ └── variable-panel-plugin/ # 变量面板插件
│ └── components/ # 变量面板组件
├── services/ # 服务层
│ ├── index.ts
│ └── custom-service.ts # 自定义服务
├── shortcuts/ # 快捷键系统
│ ├── index.ts
│ ├── constants.ts # 快捷键常量
│ ├── shortcuts.ts # 快捷键定义
│ ├── type.ts # 类型定义
│ ├── collapse/ # 折叠快捷键
│ ├── copy/ # 复制快捷键
│ ├── delete/ # 删除快捷键
│ ├── expand/ # 展开快捷键
│ ├── paste/ # 粘贴快捷键
│ ├── select-all/ # 全选快捷键
│ ├── zoom-in/ # 放大快捷键
│ └── zoom-out/ # 缩小快捷键
├── styles/ # 样式文件
├── typings/ # 类型定义
│ ├── index.ts
│ ├── json-schema.ts # JSON Schema类型
│ └── node.ts # 节点类型定义
└── utils/ # 工具函数
├── index.ts
└── on-drag-line-end.ts # 拖拽连线结束处理
```
### 关键目录功能说明
#### 1. `/components` - 组件库
* **base-node**: 所有节点的基础渲染组件
* **testrun**: 完整的测试运行功能模块,包含状态栏、表单、面板等
* **sidebar**: 侧边栏组件,提供工具和属性面板
* **node-panel**: 节点添加面板,支持拖拽添加新节点
#### 2. `/nodes` - 节点系统
每个节点类型都有独立的目录,包含:
* 节点注册信息 (`index.ts`)
* 表单元数据定义 (`form-meta.ts`)
* 节点特定的组件和逻辑
#### 3. `/plugins` - 插件系统
* **runtime-plugin**: 支持浏览器和服务器两种运行模式
* **context-menu-plugin**: 右键菜单功能
* **variable-panel-plugin**: 变量管理面板
#### 4. `/shortcuts` - 快捷键系统
完整的快捷键支持,包括:
* 基础操作:复制、粘贴、删除、全选
* 视图操作:放大、缩小、折叠、展开
* 每个快捷键都有独立的实现模块
## 应用架构设计
### 核心设计模式
#### 1. 插件化架构 (Plugin Architecture)
应用采用高度模块化的插件系统,每个功能都作为独立插件存在:
```typescript
plugins: () => [
createFreeLinesPlugin({ renderInsideLine: LineAddButton }),
createMinimapPlugin({ /* 配置 */ }),
createFreeSnapPlugin({ /* 对齐配置 */ }),
createFreeNodePanelPlugin({ renderer: NodePanel }),
createContainerNodePlugin({}),
createFreeGroupPlugin({ groupNodeRender: GroupNodeRender }),
createContextMenuPlugin({}),
createRuntimePlugin({ mode: 'browser' }),
createVariablePanelPlugin({})
]
```
#### 2. 节点注册系统 (Node Registry Pattern)
通过注册表模式管理不同类型的工作流节点:
```typescript
export const nodeRegistries: FlowNodeRegistry[] = [
ConditionNodeRegistry, // 条件节点
StartNodeRegistry, // 开始节点
EndNodeRegistry, // 结束节点
LLMNodeRegistry, // LLM节点
LoopNodeRegistry, // 循环节点
CommentNodeRegistry, // 注释节点
HTTPNodeRegistry, // HTTP节点
CodeNodeRegistry, // 代码节点
// ... 更多节点类型
];
```
#### 3. 依赖注入模式 (Dependency Injection)
使用 Inversify 框架实现服务的依赖注入:
```typescript
onBind: ({ bind }) => {
bind(CustomService).toSelf().inSingletonScope();
}
```
## 核心功能分析
### 1. 编辑器配置系统
`useEditorProps` 是整个编辑器的配置中心,包含:
```typescript
export function useEditorProps(
initialData: FlowDocumentJSON,
nodeRegistries: FlowNodeRegistry[]
): FreeLayoutProps {
return useMemo<FreeLayoutProps>(() => ({
background: true, // 背景网格
readonly: false, // 是否只读
initialData, // 初始数据
nodeRegistries, // 节点注册表
// 核心功能配置
playground: { preventGlobalGesture: true /* 阻止 mac 浏览器手势翻页 */ },
nodeEngine: { enable: true },
variableEngine: { enable: true },
history: { enable: true, enableChangeNode: true },
// 业务逻辑配置
canAddLine: (ctx, fromPort, toPort) => { /* 连线规则 */ },
canDeleteLine: (ctx, line) => { /* 删除连线规则 */ },
canDeleteNode: (ctx, node) => { /* 删除节点规则 */ },
canDropToNode: (ctx, params) => { /* 拖拽规则 */ },
// 插件配置
plugins: () => [/* 插件列表 */],
// 事件处理
onContentChange: debounce((ctx, event) => { /* 自动保存 */ }, 1000),
onInit: (ctx) => { /* 初始化 */ },
onAllLayersRendered: (ctx) => { /* 渲染完成 */ }
}), []);
}
```
### 2. 节点类型系统
应用支持多种工作流节点类型:
```typescript
export enum WorkflowNodeType {
Start = 'start', // 开始节点
End = 'end', // 结束节点
LLM = 'llm', // 大语言模型节点
HTTP = 'http', // HTTP请求节点
Code = 'code', // 代码执行节点
Variable = 'variable', // 变量节点
Condition = 'condition', // 条件判断节点
Loop = 'loop', // 循环节点
BlockStart = 'block-start', // 子画布开始节点
BlockEnd = 'block-end', // 子画布结束节点
Comment = 'comment', // 注释节点
Continue = 'continue', // 继续节点
Break = 'break', // 中断节点
}
```
每个节点都遵循统一的注册模式:
```typescript
export const StartNodeRegistry: FlowNodeRegistry = {
type: WorkflowNodeType.Start,
meta: {
isStart: true,
deleteDisable: true, // 不可删除
copyDisable: true, // 不可复制
nodePanelVisible: false, // 不在节点面板显示
defaultPorts: [{ type: 'output' }],
size: { width: 360, height: 211 }
},
info: {
icon: iconStart,
description: '工作流的起始节点,用于设置启动工作流所需的信息。'
},
formMeta, // 表单配置
canAdd() { return false; } // 不允许添加多个开始节点
};
```
### 3. 插件化架构
应用的功能通过插件系统实现模块化:
#### 核心插件列表
1. **FreeLinesPlugin** - 连线渲染和交互
2. **MinimapPlugin** - 缩略图导航
3. **FreeSnapPlugin** - 自动对齐和辅助线
4. **FreeNodePanelPlugin** - 节点添加面板
5. **ContainerNodePlugin** - 容器节点(如循环节点)
6. **FreeGroupPlugin** - 节点分组功能
7. **ContextMenuPlugin** - 右键菜单
8. **RuntimePlugin** - 工作流运行时
9. **VariablePanelPlugin** - 变量管理面板
### 4. 运行时系统
应用支持两种运行模式:
```typescript
createRuntimePlugin({
mode: 'browser', // 浏览器模式
// mode: 'server', // 服务器模式
// serverConfig: {
// domain: 'localhost',
// port: 4000,
// protocol: 'http',
// },
})
```
## 设计理念与架构优势
### 1. 高度模块化
* **插件化架构**: 每个功能都是独立插件,易于扩展和维护
* **节点注册系统**: 新节点类型可以轻松添加,无需修改核心代码
* **组件化设计**: UI组件高度复用职责清晰
### 2. 类型安全
* **完整的TypeScript支持**: 从配置到运行时的全链路类型保护
* **JSON Schema集成**: 节点数据结构通过Schema验证
* **强类型的插件接口**: 插件开发有明确的类型约束
### 3. 用户体验优化
* **实时预览**: 支持工作流的实时运行和调试
* **丰富的交互**: 拖拽、缩放、对齐、快捷键等完整的编辑体验
* **可视化反馈**: 缩略图、状态指示、连线动画等视觉反馈
### 4. 扩展性设计
* **开放的插件系统**: 第三方可以轻松开发自定义插件
* **灵活的节点系统**: 支持自定义节点类型和表单配置
* **多运行时支持**: 浏览器和服务器双模式运行
### 5. 性能优化
* **按需加载**: 组件和插件支持按需加载
* **防抖处理**: 自动保存等高频操作的性能优化
## 技术亮点
### 1. 自研编辑器框架
基于 `@flowgram.ai/free-layout-editor` 自研框架,提供:
* 自由布局的画布系统
* 完整的撤销/重做功能
* 节点和连线的生命周期管理
* 变量引擎和表达式系统
### 2. 先进的构建配置
使用 Rsbuild 作为构建工具:
```typescript
export default defineConfig({
plugins: [pluginReact(), pluginLess()],
source: {
entry: { index: './src/app.tsx' },
decorators: { version: 'legacy' } // 支持装饰器
},
tools: {
rspack: {
ignoreWarnings: [/Critical dependency/] // 忽略特定警告
}
}
});
```
### 3. 国际化支持
内置多语言支持:
```typescript
i18n: {
locale: navigator.language,
languages: {
'zh-CN': {
'Never Remind': '不再提示',
'Hold {{key}} to drag node out': '按住 {{key}} 可以将节点拖出',
},
'en-US': {},
}
}
```