feat(flow): 添加流程发布功能

- 新增 HandlerBar 组件,提供流程发布按钮
- 新增 PublishFlowModal 组件,实现流程发布表单和逻辑
- 在 FlowEditorMain 中集成发布功能,包括状态管理和事件处理
- 添加发布成功后的组件库和流程库数据更新逻辑
- 修改右侧边栏标题“组件市场”为“组件仓库”
master
钟良源 2 months ago
parent 86b9a15499
commit 8a56649019

@ -25,6 +25,8 @@ import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
import { useHistory } from './components/historyContext'; import { useHistory } from './components/historyContext';
import { NodeTypes } from '@xyflow/react'; import { NodeTypes } from '@xyflow/react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import HandlerBar from '@/pages/flowEditor/components/handlerBar';
import PublishFlowModal from '@/pages/flowEditor/components/publishFlowModal';
const edgeTypes = { const edgeTypes = {
custom: CustomEdge custom: CustomEdge
@ -142,6 +144,9 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
// 用于存储隐藏的节点ID // 用于存储隐藏的节点ID
const [hiddenNodes, setHiddenNodes] = React.useState<Set<string>>(new Set()); const [hiddenNodes, setHiddenNodes] = React.useState<Set<string>>(new Set());
// 流程发布弹窗状态
const [publishModalVisible, setPublishModalVisible] = React.useState(false);
// 监听自定义事件以隐藏/显示节点 // 监听自定义事件以隐藏/显示节点
useEffect(() => { useEffect(() => {
const handleToggleNodeVisibility = (event: CustomEvent) => { const handleToggleNodeVisibility = (event: CustomEvent) => {
@ -229,6 +234,20 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}; };
}, [undo, redo, canUndo, canRedo]); }, [undo, redo, canUndo, canRedo]);
// 处理流程发布
const handlePublish = () => {
// 先保存流程数据
saveFlowDataToServer();
// 打开发布弹窗
setPublishModalVisible(true);
};
// 发布成功后的回调
const handlePublishSuccess = () => {
setPublishModalVisible(false);
// 可以在这里添加其他逻辑,比如刷新流程列表等
};
// 监听节点和边的变化以拍摄快照 // 监听节点和边的变化以拍摄快照
useEffect(() => { useEffect(() => {
// 获取 HistoryProvider 中的 takeSnapshot 方法 // 获取 HistoryProvider 中的 takeSnapshot 方法
@ -438,6 +457,12 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
isRunning={currentAppIsRunning} isRunning={currentAppIsRunning}
></ActionBar> ></ActionBar>
</Panel> </Panel>
<Panel position="top-right">
<HandlerBar
onPublish={handlePublish}
isRunning={currentAppIsRunning}
/>
</Panel>
<AlignmentGuides /> <AlignmentGuides />
</ReactFlow> </ReactFlow>
@ -541,6 +566,16 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
/> />
</div> </div>
)} )}
{/*流程发布弹窗*/}
<PublishFlowModal
visible={publishModalVisible}
onCancel={() => setPublishModalVisible(false)}
onSuccess={handlePublishSuccess}
appId={initialData?.id}
flowId={initialData?.flowId}
currentFlowName={initialData?.name}
/>
</div> </div>
); );
}; };

@ -0,0 +1,28 @@
import React from 'react';
import { Button } from '@arco-design/web-react';
import { IconSend } from '@arco-design/web-react/icon';
interface HandlerBarProps {
onPublish?: () => void;
isRunning?: boolean;
}
const HandlerBar: React.FC<HandlerBarProps> = ({ onPublish, isRunning }) => {
return (
<div className="handlex-bar" style={{ paddingRight: 50 }}>
<div className="handlex-bar-item">
<Button
type="primary"
shape="round"
onClick={onPublish}
disabled={isRunning}
>
<IconSend />
</Button>
</div>
</div>
);
};
export default HandlerBar;

@ -0,0 +1,259 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Radio, Message } from '@arco-design/web-react';
import { appPublish } from '@/api/flow';
import { getPubFlowList, getMyFlowList } from '@/api/flow';
import { getMyComponents, getPubComponents, getTeamComponents } from '@/api/components';
import { useSelector } from 'react-redux';
import { publishType } from '@/api/interface';
import dayjs from 'dayjs';
interface PublishFlowModalProps {
visible: boolean;
onCancel: () => void;
onSuccess: () => void;
appId?: string;
flowId?: string;
currentFlowName?: string;
}
const FormItem = Form.Item;
const RadioGroup = Radio.Group;
const TextArea = Input.TextArea;
const PublishFlowModal: React.FC<PublishFlowModalProps> = ({
visible,
onCancel,
onSuccess,
appId,
flowId,
currentFlowName
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer);
// 当弹窗打开时,初始化表单数据
useEffect(() => {
if (visible) {
form.setFieldsValue({
name: currentFlowName || '',
published: 1, // 默认发布为公共流程
tag: '',
description: ''
});
}
}, [visible, currentFlowName, form]);
// 当弹窗关闭时,重置表单
useEffect(() => {
if (!visible) {
form.resetFields();
}
}, [visible, form]);
// 更新组件和流程库数据
const updateComponentData = async () => {
try {
const requests = [
{ promise: getMyComponents(), key: 'myLibs' },
{ promise: getPubComponents(), key: 'pubLibs' },
{ promise: getTeamComponents(), key: 'teamLibs' },
{
promise: getPubFlowList({
currPage: 1,
pageSize: 999
}),
key: 'pubFlow'
},
{
promise: getMyFlowList({
currPage: 1,
pageSize: 999
}),
key: 'myFlow'
}
];
const obj: any = {
myLibs: null,
pubLibs: null,
teamLibs: null,
pubFlow: null,
myFlow: null,
updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
};
const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
// 分别处理每个请求
for (const { promise, key } of requests) {
try {
const res: any = await promise;
if (res?.code === 200) {
if (key === 'pubFlow' || key === 'myFlow') {
res?.data.list.forEach(item => {
item['fontCompType'] = 'complex';
});
// 更新本地存储数据
obj[key] = res?.data.list || null;
}
// 给协同组件添加一个后续处理数据时使用的类型
else if (key === 'teamLibs') {
res.data.forEach(item => {
item.children.forEach(v => {
v['fontCompType'] = 'team';
});
});
obj[key] = res?.data || null;
}
else {
// 更新本地存储数据
res.data.forEach(item => {
item.children.forEach(v => {
v['fontCompType'] = 'normal';
});
});
obj[key] = res?.data || null;
}
}
sessionStorage.setItem(`compLibs${userInfo.userId}`, JSON.stringify(obj));
} catch (error) {
console.error(`加载${key}失败:`, error);
}
}
} catch (error) {
console.error('更新组件库失败:', error);
}
};
const handleSubmit = async () => {
try {
const values = await form.validate();
console.log('currentAppData:', currentAppData);
setLoading(true);
let publishData: publishType = {};
if (currentAppData.hasOwnProperty('parentAppId')) {
publishData = {
appId: currentAppData.parentAppId,
flowId: currentAppData.key,
name: values.name,
published: values.published,
tag: values.tag,
description: values.description,
isMain: 0 // 子流程
};
}
else {
publishData = {
appId: currentAppData.id,
flowId: '',
name: values.name,
published: values.published,
tag: values.tag,
description: values.description,
isMain: 1 // 主流程
};
}
const res: any = await appPublish(publishData);
if (res.code === 200) {
Message.success('流程发布成功');
form.resetFields();
// 发布成功后更新组件和流程库数据
await updateComponentData();
onSuccess();
}
else {
Message.error(res.msg || '流程发布失败');
}
} catch (error) {
console.error('流程发布失败:', error);
if (error?.errors) {
// 表单验证错误,不需要显示消息
return;
}
Message.error('流程发布失败');
} finally {
setLoading(false);
}
};
const handleClose = () => {
form.resetFields();
onCancel();
};
return (
<Modal
title="流程发布"
visible={visible}
onCancel={handleClose}
onOk={handleSubmit}
confirmLoading={loading}
style={{ width: 600 }}
okText="发布"
cancelText="取消"
>
<Form
form={form}
layout="vertical"
autoComplete="off"
>
<FormItem
label="流程名称"
field="name"
rules={[
{ required: true, message: '请输入流程名称' },
{ maxLength: 50, message: '流程名称不能超过50个字符' }
]}
>
<Input placeholder="请输入流程名称" maxLength={50} />
</FormItem>
<FormItem
label="发布类型"
field="published"
rules={[{ required: true, message: '请选择发布类型' }]}
>
<RadioGroup>
<Radio value={0}></Radio>
<Radio value={1}></Radio>
</RadioGroup>
</FormItem>
<FormItem
label="标签"
field="tag"
rules={[
{ maxLength: 20, message: '标签不能超过20个字符' }
]}
>
<Input placeholder="请输入流程标签,如:数据处理、图像识别等" maxLength={20} />
</FormItem>
<FormItem
label="流程描述"
field="description"
rules={[
{ maxLength: 200, message: '流程描述不能超过200个字符' }
]}
>
<TextArea
placeholder="请输入流程描述,说明流程的功能和用途"
maxLength={200}
showWordLimit
autoSize={{ minRows: 3, maxRows: 6 }}
/>
</FormItem>
</Form>
</Modal>
);
};
export default PublishFlowModal;

@ -87,7 +87,7 @@ const RightSideBar: React.FC<RightSideBarProps> = ({ updateProjectComp }) => {
title={ title={
<span onClick={() => handleTabClick('2')}> <span onClick={() => handleTabClick('2')}>
<IconRobot style={{ fontSize: 16 }} /> <IconRobot style={{ fontSize: 16 }} />
<span></span> <span></span>
</span> </span>
} }
> >

Loading…
Cancel
Save