From 9da3978f912f7f14c018c5eea9ed85c14462f946 Mon Sep 17 00:00:00 2001 From: ayou <550244300@qq.com> Date: Sat, 30 Aug 2025 00:19:26 +0800 Subject: [PATCH] =?UTF-8?q?docs(frontend):=20=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E7=94=B1=E5=B8=83=E5=B1=80=E7=BC=96=E8=BE=91=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E6=BC=94=E7=A4=BA=E6=96=87=E6=A1=A3=E5=92=8C=E6=9C=80=E4=BD=B3?= =?UTF-8?q?=E5=AE=9E=E8=B7=B5=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 flow-free-layout-demo.md 和 flow-free-layout-simple-demo.md 文档 包含编辑器架构设计、核心功能、安装指南和示例代码 --- frontend/flow-fixed-layout-demo.md | 0 frontend/flow-free-layout-base-demo.md | 701 +++++++++++++++++++++++ frontend/flow-free-layout-demo.md | 0 frontend/flow-free-layout-simple-demo.md | 412 +++++++++++++ 4 files changed, 1113 insertions(+) create mode 100644 frontend/flow-fixed-layout-demo.md create mode 100644 frontend/flow-free-layout-base-demo.md create mode 100644 frontend/flow-free-layout-demo.md create mode 100644 frontend/flow-free-layout-simple-demo.md diff --git a/frontend/flow-fixed-layout-demo.md b/frontend/flow-fixed-layout-demo.md new file mode 100644 index 0000000..e69de29 diff --git a/frontend/flow-free-layout-base-demo.md b/frontend/flow-free-layout-base-demo.md new file mode 100644 index 0000000..5cd12e5 --- /dev/null +++ b/frontend/flow-free-layout-base-demo.md @@ -0,0 +1,701 @@ +# 基础用法 + +import { FreeLayoutSimplePreview } from '../../../../components'; + + + +## 功能介绍 + +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( + () => ({ + // 启用背景网格 + background: true, + // 非只读模式 + readonly: false, + // 初始数据 + initialData, + // 节点类型注册 + nodeRegistries, + // 默认节点注册 + getNodeDefaultRegistry(type) { + return { + type, + meta: { + defaultExpanded: true, + }, + formMeta: { + // 节点表单渲染 + render: () => ( + <> + name="title"> + {({ field }) =>
{field.value}
} + +
+ name="content"> + + +
+ + ), + }, + }; + }, + // 节点渲染 + materials: { + renderDefaultNode: (props: WorkflowNodeProps) => { + const { form } = useNodeRender(); + return ( + + {form?.render()} + + ); + }, + }, + // 内容变更回调 + 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); + + return ( +
+ {nodeTypes.map(nodeType => ( +
dragService.startDragCard(nodeType, e, { + data: { + title: nodeType, + content: '拖拽创建的节点' + } + })} + > + {nodeType} +
+ ))} +
+ ); +}; +``` + +#### 步骤五:创建工具栏和缩略图 + +```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 ( +
+ + + + + + + {Math.floor(tools.zoom * 100)}% +
+ ); +}; + +// 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 ( +
+ +
+ ); +}; +``` + +#### 步骤六:组装编辑器主组件 + +```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 ( + +
+
+ + +
+ + +
+
+ ); +}; +``` + +#### 步骤七:创建应用入口 + +```tsx +// src/app.tsx +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { Editor } from './editor'; + +ReactDOM.render(, 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, // 监听节点数据变化 + } +}; + +// 完整编辑器渲染 + +
+ {/* 节点添加面板 */} + {/* 核心编辑器渲染区域 */} + {/* 工具栏 */} + {/* 缩略图 */} +
+
+``` + +### 4. 核心钩子函数 + +在组件中可以使用多种钩子函数获取和操作编辑器: + +```tsx +// 获取拖拽服务 +const dragService = useService(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 diff --git a/frontend/flow-free-layout-demo.md b/frontend/flow-free-layout-demo.md new file mode 100644 index 0000000..e69de29 diff --git a/frontend/flow-free-layout-simple-demo.md b/frontend/flow-free-layout-simple-demo.md new file mode 100644 index 0000000..e1f7c9b --- /dev/null +++ b/frontend/flow-free-layout-simple-demo.md @@ -0,0 +1,412 @@ +# 最佳实践 + +import { FreeFeatureOverview } from '../../../../components'; + + + +## 安装 + +```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(() => ({ + 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': {}, + } +} +```