feat(flows): 新增流程编辑器基础功能与相关组件

feat(backend): 添加流程模型与服务支持
feat(frontend): 实现流程编辑器UI与交互
feat(assets): 添加流程节点图标资源
feat(plugins): 实现上下文菜单和运行时插件
feat(components): 新增基础节点和侧边栏组件
feat(routes): 添加流程相关路由配置
feat(models): 创建流程和运行日志数据模型
feat(services): 实现流程服务层逻辑
feat(migration): 添加流程相关数据库迁移
feat(config): 更新前端配置支持流程编辑器
feat(utils): 增强axios错误处理和工具函数
This commit is contained in:
2025-09-15 00:27:13 +08:00
parent 9da3978f91
commit b0963e5e37
291 changed files with 17947 additions and 86 deletions

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { useContext, useState, useEffect } from 'react';
import { useClientContext, CommandService } from '@flowgram.ai/free-layout-editor';
import { Button } from '@douyinfe/semi-ui';
import { IconClose, IconSmallTriangleDown, IconSmallTriangleLeft } from '@douyinfe/semi-icons';
import { toggleLoopExpanded } from '../../utils';
import { FlowCommandId } from '../../shortcuts';
import { useIsSidebar, useNodeRenderContext } from '../../hooks';
import { SidebarContext } from '../../context';
import { NodeMenu } from '../../components/node-menu';
import { getIcon } from './utils';
import { TitleInput } from './title-input';
import { Header, Operators } from './styles';
export function FormHeader() {
const { node, expanded, toggleExpand, readonly } = useNodeRenderContext();
const [titleEdit, updateTitleEdit] = useState<boolean>(false);
const ctx = useClientContext();
const { setNodeId } = useContext(SidebarContext);
const isSidebar = useIsSidebar();
const handleExpand = (e: React.MouseEvent) => {
toggleExpand();
e.stopPropagation(); // Disable clicking prevents the sidebar from opening
};
const handleDelete = () => {
ctx.get<CommandService>(CommandService).executeCommand(FlowCommandId.DELETE, [node]);
};
const handleClose = () => {
setNodeId(undefined);
};
useEffect(() => {
// 折叠 loop 子节点
if (node.flowNodeType === 'loop') {
toggleLoopExpanded(node, expanded);
}
}, [expanded]);
return (
<Header>
{getIcon(node)}
<TitleInput readonly={readonly} updateTitleEdit={updateTitleEdit} titleEdit={titleEdit} />
{node.renderData.expandable && !isSidebar && (
<Button
type="primary"
icon={expanded ? <IconSmallTriangleDown /> : <IconSmallTriangleLeft />}
size="small"
theme="borderless"
onClick={handleExpand}
/>
)}
{readonly ? undefined : (
<Operators>
<NodeMenu node={node} deleteNode={handleDelete} updateTitleEdit={updateTitleEdit} />
</Operators>
)}
{isSidebar && (
<Button
type="primary"
icon={<IconClose />}
size="small"
theme="borderless"
onClick={handleClose}
/>
)}
</Header>
);
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import styled from 'styled-components';
export const Header = styled.div`
box-sizing: border-box;
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
column-gap: 8px;
border-radius: 8px 8px 0 0;
cursor: move;
background: linear-gradient(#f2f2ff 0%, rgb(251, 251, 251) 100%);
overflow: hidden;
padding: 8px;
`;
export const Title = styled.div`
font-size: 20px;
flex: 1;
width: 0;
`;
export const Icon = styled.img`
width: 24px;
height: 24px;
scale: 0.8;
border-radius: 4px;
`;
export const Operators = styled.div`
display: flex;
align-items: center;
column-gap: 4px;
`;

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { useRef, useEffect } from 'react';
import { Field, FieldRenderProps } from '@flowgram.ai/free-layout-editor';
import { Typography, Input } from '@douyinfe/semi-ui';
import { Title } from './styles';
import { Feedback } from '../feedback';
const { Text } = Typography;
export function TitleInput(props: {
readonly: boolean;
titleEdit: boolean;
updateTitleEdit: (setEdit: boolean) => void;
}): JSX.Element {
const { readonly, titleEdit, updateTitleEdit } = props;
const ref = useRef<any>();
const titleEditing = titleEdit && !readonly;
useEffect(() => {
if (titleEditing) {
ref.current?.focus();
}
}, [titleEditing]);
return (
<Title>
<Field name="title">
{({ field: { value, onChange }, fieldState }: FieldRenderProps<string>) => (
<div style={{ height: 24 }}>
{titleEditing ? (
<Input
value={value}
onChange={onChange}
ref={ref}
onBlur={() => updateTitleEdit(false)}
/>
) : (
<Text ellipsis={{ showTooltip: true }}>{value}</Text>
)}
<Feedback errors={fieldState?.errors} />
</div>
)}
</Field>
</Title>
);
}

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { type FlowNodeEntity } from '@flowgram.ai/free-layout-editor';
import { FlowNodeRegistry } from '../../typings';
import { Icon } from './styles';
export const getIcon = (node: FlowNodeEntity) => {
const icon = node.getNodeRegistry<FlowNodeRegistry>().info?.icon;
if (!icon) return null;
return <Icon src={icon} />;
};