mirror of
https://github.com/whyour/qinglong.git
synced 2025-11-10 00:26:09 +08:00
Add comprehensive Flowgram migration guide with full implementation examples
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
parent
f04ae7611c
commit
ff74e96cbf
692
FLOWGRAM_MIGRATION_GUIDE.md
Normal file
692
FLOWGRAM_MIGRATION_GUIDE.md
Normal file
|
|
@ -0,0 +1,692 @@
|
||||||
|
# Flowgram Migration Guide - 从 React Flow 迁移到 Flowgram.ai
|
||||||
|
|
||||||
|
## 概述 (Overview)
|
||||||
|
|
||||||
|
本文档提供从 React Flow 迁移到 Flowgram.ai 的完整指南。
|
||||||
|
|
||||||
|
参考 Demo: https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout
|
||||||
|
|
||||||
|
## 1. 依赖安装 (Dependencies Installation)
|
||||||
|
|
||||||
|
### 已添加的依赖 (Dependencies Added)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"@flowgram.ai/core": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-layout-editor": "^1.0.2",
|
||||||
|
"@flowgram.ai/reactive": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-snap-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-lines-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-node-panel-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/minimap-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-container-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-group-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/form-materials": "^1.0.2",
|
||||||
|
"@flowgram.ai/panel-manager-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/free-stack-plugin": "^1.0.2",
|
||||||
|
"@flowgram.ai/runtime-interface": "^1.0.2",
|
||||||
|
"@flowgram.ai/runtime-js": "^1.0.2",
|
||||||
|
"nanoid": "^5.0.9"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安装命令 (Installation Command)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/runner/work/qinglong/qinglong
|
||||||
|
npm install --legacy-peer-deps
|
||||||
|
# or
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 数据结构适配 (Data Structure Adaptation)
|
||||||
|
|
||||||
|
### 当前 React Flow 格式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: "trigger-1",
|
||||||
|
type: "trigger",
|
||||||
|
position: { x: 100, y: 100 },
|
||||||
|
data: {
|
||||||
|
label: "Webhook Trigger",
|
||||||
|
triggerType: "webhook",
|
||||||
|
config: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
id: "e1",
|
||||||
|
source: "trigger-1",
|
||||||
|
target: "condition-1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flowgram 格式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: "trigger_1",
|
||||||
|
type: "trigger",
|
||||||
|
meta: {
|
||||||
|
position: { x: 100, y: 100 }
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: "Webhook Trigger",
|
||||||
|
triggerType: "webhook",
|
||||||
|
config: {},
|
||||||
|
// Flowgram 使用 inputs/outputs schema
|
||||||
|
outputs: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
data: { type: "object" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
sourceNodeID: "trigger_1",
|
||||||
|
targetNodeID: "condition_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 节点注册系统 (Node Registry System)
|
||||||
|
|
||||||
|
### 节点注册接口 (Node Registry Interface)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/pages/scenario/flowgram/types.ts
|
||||||
|
import { FlowNodeRegistry as FlowgramRegistry } from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
export type FlowNodeRegistry = FlowgramRegistry;
|
||||||
|
|
||||||
|
export interface NodeData {
|
||||||
|
title: string;
|
||||||
|
inputs?: any;
|
||||||
|
outputs?: any;
|
||||||
|
inputsValues?: any;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建节点注册 (Create Node Registries)
|
||||||
|
|
||||||
|
#### Start 节点 (src/pages/scenario/flowgram/nodes/start.tsx)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { FlowNodeRegistry } from '../types';
|
||||||
|
|
||||||
|
export const StartNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'start',
|
||||||
|
meta: {
|
||||||
|
category: 'basic',
|
||||||
|
label: '开始',
|
||||||
|
description: '工作流开始节点',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '开始',
|
||||||
|
outputs: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerData: {
|
||||||
|
type: 'object',
|
||||||
|
description: '触发数据',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: '标题',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Trigger 节点 (src/pages/scenario/flowgram/nodes/trigger.tsx)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const TriggerNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'trigger',
|
||||||
|
meta: {
|
||||||
|
category: 'triggers',
|
||||||
|
label: '触发器',
|
||||||
|
description: '各种触发器类型',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '触发器',
|
||||||
|
triggerType: 'webhook',
|
||||||
|
outputs: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
data: { type: 'object' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: '标题',
|
||||||
|
},
|
||||||
|
triggerType: {
|
||||||
|
type: 'string',
|
||||||
|
title: '触发类型',
|
||||||
|
enum: ['time', 'webhook', 'variable', 'task_status', 'system_event'],
|
||||||
|
enumNames: ['时间触发', 'Webhook', '变量监听', '任务状态', '系统事件'],
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
type: 'object',
|
||||||
|
title: '配置',
|
||||||
|
properties: {
|
||||||
|
schedule: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Cron 表达式',
|
||||||
|
},
|
||||||
|
filePath: {
|
||||||
|
type: 'string',
|
||||||
|
title: '文件路径',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Condition 节点
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const ConditionNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'condition',
|
||||||
|
meta: {
|
||||||
|
category: 'logic',
|
||||||
|
label: '条件',
|
||||||
|
description: '条件判断节点',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '条件',
|
||||||
|
conditions: [],
|
||||||
|
outputs: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
result: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: { type: 'string', title: '标题' },
|
||||||
|
operator: {
|
||||||
|
type: 'string',
|
||||||
|
title: '操作符',
|
||||||
|
enum: ['equals', 'not_equals', 'greater_than', 'less_than', 'contains', 'not_contains'],
|
||||||
|
enumNames: ['等于', '不等于', '大于', '小于', '包含', '不包含'],
|
||||||
|
},
|
||||||
|
field: { type: 'string', title: '字段名' },
|
||||||
|
value: { type: 'string', title: '比较值' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Action 节点
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const ActionNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'action',
|
||||||
|
meta: {
|
||||||
|
category: 'actions',
|
||||||
|
label: '动作',
|
||||||
|
description: '执行动作',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '动作',
|
||||||
|
actionType: 'run_task',
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: { type: 'string', title: '标题' },
|
||||||
|
actionType: {
|
||||||
|
type: 'string',
|
||||||
|
title: '动作类型',
|
||||||
|
enum: ['run_task', 'set_variable', 'execute_command', 'send_notification'],
|
||||||
|
enumNames: ['运行任务', '设置变量', '执行命令', '发送通知'],
|
||||||
|
},
|
||||||
|
cronId: { type: 'number', title: '任务 ID' },
|
||||||
|
name: { type: 'string', title: '变量名' },
|
||||||
|
value: { type: 'string', title: '变量值' },
|
||||||
|
command: { type: 'string', title: '命令' },
|
||||||
|
message: { type: 'string', title: '消息' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Control 节点
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const ControlNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'control',
|
||||||
|
meta: {
|
||||||
|
category: 'control',
|
||||||
|
label: '控制流',
|
||||||
|
description: '控制流节点',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '控制流',
|
||||||
|
controlType: 'delay',
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: { type: 'string', title: '标题' },
|
||||||
|
controlType: {
|
||||||
|
type: 'string',
|
||||||
|
title: '控制类型',
|
||||||
|
enum: ['delay', 'retry', 'circuit_breaker'],
|
||||||
|
enumNames: ['延迟', '重试', '熔断器'],
|
||||||
|
},
|
||||||
|
delaySeconds: { type: 'number', title: '延迟秒数' },
|
||||||
|
maxRetries: { type: 'number', title: '最大重试次数' },
|
||||||
|
retryDelay: { type: 'number', title: '重试延迟' },
|
||||||
|
backoffMultiplier: { type: 'number', title: '退避倍数' },
|
||||||
|
failureThreshold: { type: 'number', title: '失败阈值' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logic Gate 节点
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const LogicGateNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'logic_gate',
|
||||||
|
meta: {
|
||||||
|
category: 'logic',
|
||||||
|
label: '逻辑门',
|
||||||
|
description: 'AND/OR 逻辑',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '逻辑门',
|
||||||
|
gateType: 'AND',
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: { type: 'string', title: '标题' },
|
||||||
|
gateType: {
|
||||||
|
type: 'string',
|
||||||
|
title: '逻辑类型',
|
||||||
|
enum: ['AND', 'OR'],
|
||||||
|
enumNames: ['AND', 'OR'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### End 节点
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const EndNodeRegistry: FlowNodeRegistry = {
|
||||||
|
type: 'end',
|
||||||
|
meta: {
|
||||||
|
category: 'basic',
|
||||||
|
label: '结束',
|
||||||
|
description: '工作流结束节点',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: '结束',
|
||||||
|
inputs: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
result: { type: 'any' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formMeta: {
|
||||||
|
properties: {
|
||||||
|
title: { type: 'string', title: '标题' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 编辑器组件 (Editor Component)
|
||||||
|
|
||||||
|
### 主编辑器 (src/pages/scenario/flowgram/Editor.tsx)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import React from 'react';
|
||||||
|
import { EditorRenderer, FreeLayoutEditorProvider } from '@flowgram.ai/free-layout-editor';
|
||||||
|
import '@flowgram.ai/free-layout-editor/index.css';
|
||||||
|
import { useEditorProps } from './hooks/useEditorProps';
|
||||||
|
import { nodeRegistries } from './nodes';
|
||||||
|
|
||||||
|
interface EditorProps {
|
||||||
|
initialData: any;
|
||||||
|
onSave: (data: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FlowgramEditor: React.FC<EditorProps> = ({ initialData, onSave }) => {
|
||||||
|
const editorProps = useEditorProps(initialData, nodeRegistries, onSave);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', height: '600px' }}>
|
||||||
|
<FreeLayoutEditorProvider {...editorProps}>
|
||||||
|
<EditorRenderer />
|
||||||
|
</FreeLayoutEditorProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编辑器配置 (src/pages/scenario/flowgram/hooks/useEditorProps.tsx)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
|
||||||
|
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
|
||||||
|
import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
|
||||||
|
import { createFreeNodePanelPlugin } from '@flowgram.ai/free-node-panel-plugin';
|
||||||
|
import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';
|
||||||
|
import { FlowNodeRegistry } from '../types';
|
||||||
|
|
||||||
|
export function useEditorProps(
|
||||||
|
initialData: any,
|
||||||
|
nodeRegistries: FlowNodeRegistry[],
|
||||||
|
onSave: (data: any) => void
|
||||||
|
): FreeLayoutProps {
|
||||||
|
return useMemo<FreeLayoutProps>(
|
||||||
|
() => ({
|
||||||
|
background: true,
|
||||||
|
readonly: false,
|
||||||
|
twoWayConnection: true,
|
||||||
|
initialData,
|
||||||
|
nodeRegistries,
|
||||||
|
|
||||||
|
// 节点数据转换
|
||||||
|
fromNodeJSON(node, json) {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
|
||||||
|
toNodeJSON(node, json) {
|
||||||
|
return json;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 连线颜色
|
||||||
|
lineColor: {
|
||||||
|
default: '#4d53e8',
|
||||||
|
drawing: '#5DD6E3',
|
||||||
|
hovered: '#37d0ff',
|
||||||
|
selected: '#37d0ff',
|
||||||
|
error: 'red',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 连线规则
|
||||||
|
canAddLine(ctx, fromPort, toPort) {
|
||||||
|
if (fromPort.node === toPort.node) return false;
|
||||||
|
return !fromPort.node.lines.allInputNodes.includes(toPort.node);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 内容变化回调
|
||||||
|
onContentChange: (ctx, event) => {
|
||||||
|
if (ctx.document.disposed) return;
|
||||||
|
const data = ctx.document.toJSON();
|
||||||
|
onSave(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 插件
|
||||||
|
plugins: () => [
|
||||||
|
createMinimapPlugin({}),
|
||||||
|
createFreeSnapPlugin({}),
|
||||||
|
createFreeNodePanelPlugin({}),
|
||||||
|
createFreeLinesPlugin({}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[initialData, nodeRegistries, onSave]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 模态框集成 (Modal Integration)
|
||||||
|
|
||||||
|
### 替换 visualWorkflowModal.tsx
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/pages/scenario/flowgramWorkflowModal.tsx
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Modal } from 'antd';
|
||||||
|
import { FlowgramEditor } from './flowgram/Editor';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
interface FlowgramWorkflowModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
scenario?: any;
|
||||||
|
onOk: (scenario: any) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FlowgramWorkflowModal: React.FC<FlowgramWorkflowModalProps> = ({
|
||||||
|
visible,
|
||||||
|
scenario,
|
||||||
|
onOk,
|
||||||
|
onCancel,
|
||||||
|
}) => {
|
||||||
|
const [workflowData, setWorkflowData] = useState<any>(null);
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scenario) {
|
||||||
|
setName(scenario.name || '');
|
||||||
|
setWorkflowData(scenario.workflowGraph || getInitialData());
|
||||||
|
} else {
|
||||||
|
setName('');
|
||||||
|
setWorkflowData(getInitialData());
|
||||||
|
}
|
||||||
|
}, [scenario, visible]);
|
||||||
|
|
||||||
|
const getInitialData = () => ({
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'start_0',
|
||||||
|
type: 'start',
|
||||||
|
meta: { position: { x: 100, y: 300 } },
|
||||||
|
data: { title: '开始' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'end_0',
|
||||||
|
type: 'end',
|
||||||
|
meta: { position: { x: 800, y: 300 } },
|
||||||
|
data: { title: '结束' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = (data: any) => {
|
||||||
|
setWorkflowData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
onOk({
|
||||||
|
...scenario,
|
||||||
|
name,
|
||||||
|
workflowGraph: workflowData,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={scenario ? intl.get('编辑场景') : intl.get('新建场景')}
|
||||||
|
open={visible}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
width="90%"
|
||||||
|
style={{ top: 20 }}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<input
|
||||||
|
placeholder="场景名称"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
style={{ width: '100%', padding: '8px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{workflowData && (
|
||||||
|
<FlowgramEditor
|
||||||
|
initialData={workflowData}
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 在主页面中使用 (Usage in Main Page)
|
||||||
|
|
||||||
|
### 更新 index.tsx
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/pages/scenario/index.tsx
|
||||||
|
import { FlowgramWorkflowModal } from './flowgramWorkflowModal';
|
||||||
|
|
||||||
|
// 替换原来的
|
||||||
|
// import { VisualWorkflowModal } from './visualWorkflowModal';
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
<FlowgramWorkflowModal
|
||||||
|
visible={modalVisible}
|
||||||
|
scenario={editingScenario}
|
||||||
|
onOk={handleSaveScenario}
|
||||||
|
onCancel={() => setModalVisible(false)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 后端兼容性 (Backend Compatibility)
|
||||||
|
|
||||||
|
### 数据转换工具 (Data Conversion)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/pages/scenario/flowgram/utils/dataConverter.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Flowgram 格式转换为后端格式
|
||||||
|
*/
|
||||||
|
export function convertFlowgramToBackend(flowgramData: any) {
|
||||||
|
return {
|
||||||
|
nodes: flowgramData.nodes.map((node: any) => ({
|
||||||
|
id: node.id,
|
||||||
|
type: node.type,
|
||||||
|
position: node.meta?.position || { x: 0, y: 0 },
|
||||||
|
data: node.data,
|
||||||
|
})),
|
||||||
|
edges: flowgramData.edges.map((edge: any) => ({
|
||||||
|
id: `${edge.sourceNodeID}-${edge.targetNodeID}`,
|
||||||
|
source: edge.sourceNodeID,
|
||||||
|
target: edge.targetNodeID,
|
||||||
|
sourcePort: edge.sourcePortID,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将后端格式转换为 Flowgram 格式
|
||||||
|
*/
|
||||||
|
export function convertBackendToFlowgram(backendData: any) {
|
||||||
|
return {
|
||||||
|
nodes: backendData.nodes.map((node: any) => ({
|
||||||
|
id: node.id,
|
||||||
|
type: node.type,
|
||||||
|
meta: {
|
||||||
|
position: node.position,
|
||||||
|
},
|
||||||
|
data: node.data,
|
||||||
|
})),
|
||||||
|
edges: backendData.edges.map((edge: any) => ({
|
||||||
|
sourceNodeID: edge.source,
|
||||||
|
targetNodeID: edge.target,
|
||||||
|
sourcePortID: edge.sourcePort,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. CSS 样式 (Styles)
|
||||||
|
|
||||||
|
### 导入 Flowgram 样式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/pages/scenario/flowgram/Editor.tsx
|
||||||
|
import '@flowgram.ai/free-layout-editor/index.css';
|
||||||
|
|
||||||
|
// 自定义样式
|
||||||
|
const customStyles = `
|
||||||
|
.flowgram-editor {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 测试清单 (Testing Checklist)
|
||||||
|
|
||||||
|
- [ ] 依赖安装成功
|
||||||
|
- [ ] 节点可以正常创建和拖拽
|
||||||
|
- [ ] 节点可以连接
|
||||||
|
- [ ] 节点配置面板正常显示
|
||||||
|
- [ ] 保存功能正常
|
||||||
|
- [ ] 与后端图执行引擎兼容
|
||||||
|
- [ ] 可以编辑现有场景
|
||||||
|
- [ ] 所有节点类型都可用
|
||||||
|
|
||||||
|
## 10. 故障排查 (Troubleshooting)
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
**Q: Flowgram 插件包无法安装**
|
||||||
|
A: 使用 `npm install --legacy-peer-deps` 或 `pnpm install`
|
||||||
|
|
||||||
|
**Q: TypeScript 类型错误**
|
||||||
|
A: 添加类型声明文件或使用 `// @ts-ignore`
|
||||||
|
|
||||||
|
**Q: 样式不显示**
|
||||||
|
A: 确保导入了 `@flowgram.ai/free-layout-editor/index.css`
|
||||||
|
|
||||||
|
**Q: 节点不显示**
|
||||||
|
A: 检查节点注册是否正确,formMeta 是否完整
|
||||||
|
|
||||||
|
## 11. 参考资源 (Resources)
|
||||||
|
|
||||||
|
- Flowgram 官方 Demo: https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout
|
||||||
|
- Flowgram 文档: https://flowgram.ai/
|
||||||
|
- 当前实现: src/pages/scenario/visualWorkflowModal.tsx (React Flow 版本)
|
||||||
|
|
||||||
|
## 总结 (Summary)
|
||||||
|
|
||||||
|
这个迁移涉及:
|
||||||
|
1. 替换依赖包(React Flow -> Flowgram)
|
||||||
|
2. 重写节点注册系统
|
||||||
|
3. 重新实现编辑器组件
|
||||||
|
4. 适配数据格式
|
||||||
|
5. 保持后端兼容性
|
||||||
|
|
||||||
|
预计工作量:8-12 小时
|
||||||
|
难度:中等
|
||||||
|
风险:中等(主要是 Flowgram 文档不完整)
|
||||||
Loading…
Reference in New Issue
Block a user