flowgram.ai/apps/docs/src/zh/guide/getting-started/create-fixed-layout-simple.mdx
2025-05-28 03:07:15 +00:00

373 lines
9.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 创建固定布局画布
本案例可通过 `npx @flowgram.ai/create-app@latest fixed-layout-simple` 安装,完整代码及效果见:
<div className="rs-tip">
<a className="rs-link" href="/examples/fixed-layout/fixed-layout-simple.html">
固定布局基础用法
</a>
</div>
文件结构:
```
- hooks
- use-editor-props.ts # 画布配置
- components
- base-node.tsx # 节点渲染
- tools.tsx # 画布工具栏
- initial-data.ts # 初始化数据
- node-registries.ts # 节点配置
- app.tsx # 画布入口
```
### 1. 画布入口
- `FixedLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
- `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置
```tsx pure title="app.tsx"
import {
FixedLayoutEditorProvider,
EditorRenderer,
} from '@flowgram.ai/fixed-layout-editor';
import '@flowgram.ai/fixed-layout-editor/index.css'; // 加载样式
import { useEditorProps } from './hooks/use-editor-props' // 画布详细的 props 配置
import { Tools } from './components/tools' // 画布工具
function App() {
const editorProps = useEditorProps()
return (
<FixedLayoutEditorProvider {...editorProps}>
<EditorRenderer className="demo-editor" />
<Tools />
</FixedLayoutEditorProvider>
);
}
```
### 2. 配置画布
画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
```tsx pure title="hooks/use-editor-props.tsx"
import { useMemo } from 'react';
import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
import { initialData } from './initial-data' // 初始化数据
import { nodeRegistries } from './node-registries' // 节点声明配置
import { BaseNode } from './base-node' // 节点渲染
export function useEditorProps(
): FixedLayoutProps {
return useMemo<FixedLayoutProps>(
() => ({
/**
* 初始化数据
*/
initialData,
/**
* 画布节点定义
*/
nodeRegistries,
/**
* 可以通过 key 自定义 UI 组件, 比如添加按钮,这里提供了一套 semi 组件方便快速验证, 如果需要深度定制,参考:
* https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
*/
materials: {
components: {
...defaultFixedSemiMaterials,
// [FlowRendererKey.ADDER]: NodeAdder,
// [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
},
renderDefaultNode: BaseNode, // 节点渲染组件
},
/**
* 节点引擎, 用于渲染节点表单
*/
nodeEngine: {
enable: true,
},
/**
* 画布历史记录, 用于控制 redo/undo
*/
history: {
enable: true,
enableChangeNode: true, // 用于监听节点表单数据变化
},
/**
* 画布初始化回调
*/
onInit: ctx => {
// 如果要动态加载数据,可以通过如下方法异步执行
// ctx.docuemnt.fromJSON(initialData)
},
/**
* 画布第一次渲染完成回调
*/
onAllLayersRendered: (ctx) => {},
/**
* 画布销毁回调
*/
onDispose: () => { },
plugins: () => [
/**
* 缩略图插件
*/
createMinimapPlugin({}),
],
}),
[],
);
}
```
### 3. 配置数据
画布文档数据采用树形结构,支持嵌套
:::note 文档数据基本结构:
- nodes `array` 节点列表, 支持嵌套
:::
:::note 节点数据基本结构:
- id: `string` 节点唯一标识, 必须保证唯一
- meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
- type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
- data: `object` 节点表单数据
- blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
:::
```tsx pure title="initial-data.tsx"
import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
/**
* 配置流程数据,数据为 blocks 嵌套的格式
*/
export const initialData: FlowDocumentJSON = {
nodes: [
// 开始节点
{
id: 'start_0',
type: 'start',
data: {
title: 'Start',
content: 'start content'
},
blocks: [],
},
// 分支节点
{
id: 'condition_0',
type: 'condition',
data: {
title: 'Condition'
},
blocks: [
{
id: 'branch_0',
type: 'block',
data: {
title: 'Branch 0',
content: 'branch 1 content'
},
blocks: [
{
id: 'custom_0',
type: 'custom',
data: {
title: 'Custom',
content: 'custrom content'
},
},
],
},
{
id: 'branch_1',
type: 'block',
data: {
title: 'Branch 1',
content: 'branch 1 content'
},
blocks: [],
},
],
},
// 结束节点
{
id: 'end_0',
type: 'end',
data: {
title: 'End',
content: 'end content'
},
},
],
};
```
### 4. 声明节点
声明节点可以用于确定节点的类型及渲染方式
```tsx pure title="node-registries.tsx"
import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
/**
* 自定义节点注册
*/
export const nodeRegistries: FlowNodeRegistry[] = [
{
/**
* 自定义节点类型
*/
type: 'condition',
/**
* 自定义节点扩展:
* - loop: 扩展为循环节点
* - start: 扩展为开始节点
* - dynamicSplit: 扩展为分支节点
* - end: 扩展为结束节点
* - tryCatch: 扩展为 tryCatch 节点
* - default: 扩展为普通节点 (默认)
*/
extend: 'dynamicSplit',
/**
* 节点配置信息
*/
meta: {
// isStart: false, // 是否为开始节点
// isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
// draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
// selectable: false, // 触发器等开始节点不能被框选
// deleteDisable: true, // 禁止删除
// copyDisable: true, // 禁止copy
// addDisable: true, // 禁止添加
},
/**
* 配置节点表单的校验及渲染,
* 注validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
*/
formMeta: {
validateTrigger: ValidateTrigger.onChange,
validate: {
title: ({ value }) => (value ? undefined : 'Title is required'),
},
/**
* Render form
*/
render: () => (
<>
<Field name="title">
{({ field }) => <div className="demo-free-node-title">{field.value}</div>}
</Field>
<Field name="content">
{({ field }) => <input onChange={field.onChange} value={field.value}/>}
</Field>
</>
)
},
},
];
```
### 5. 渲染节点
渲染节点用于添加样式、事件及表单渲染的位置
```tsx pure title="components/base-node.tsx"
import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
export const BaseNode = () => {
/**
* 提供节点渲染相关的方法
*/
const nodeRender = useNodeRender();
/**
* 只有在节点引擎开启时候才能使用表单
*/
const form = nodeRender.form;
return (
<div
className="demo-fixed-node"
onMouseEnter={nodeRender.onMouseEnter}
onMouseLeave={nodeRender.onMouseLeave}
onMouseDown={e => {
// 触发拖拽
nodeRender.startDrag(e);
e.stopPropagation();
}}
style={{
// BlockOrderIcon 表示为分支的第一个节点BlockIcon 则表示整个 condition 的头部节点
...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
outline: form?.state.invalid ? '1px solid red' : 'none', // 表单校验错误让边框标红
}}
>
{
// 表单渲染通过 formMeta 生成
form?.render()
}
</div>
);
};
```
### 6. 添加工具
工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
```tsx pure title="components/tools.tsx"
import { useEffect, useState } from 'react'
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
export function Tools() {
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: 16, 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.changeLayout()}>ChangeLayout</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>
}
```
### 7. 效果
import { FixedLayoutSimple } from '../../../../components';
<div style={{ position: 'relative', width: '100%', height: '600px'}}>
<FixedLayoutSimple />
</div>