feat(flowEditor): 添加流程运行功能及WebSocket支持

- 在ActionBar组件中增加运行/停止按钮及状态控制
- 实现流程运行时的WebSocket连接管理
- 添加useWebSocket自定义hook处理WebSocket通信- 支持运行状态切换和实时消息处理
- 集成用户令牌认证和WebSocket地址配置
- 提供运行启动和停止的完整生命周期管理
master
钟良源 4 months ago
parent 11c796aa75
commit 0230119987

@ -0,0 +1,131 @@
import { useState, useEffect, useRef, useCallback } from 'react';
interface WebSocketOptions {
reconnectInterval?: number;
maxReconnectAttempts?: number;
onOpen?: (event: Event) => void;
onClose?: (event: CloseEvent) => void;
onError?: (event: Event) => void;
onMessage?: (event: MessageEvent) => void;
}
interface WebSocketHook {
connect: (url: string) => void;
disconnect: () => void;
sendMessage: (message: string | object) => void;
readyState: number;
isConnected: boolean;
}
const useWebSocket = (options: WebSocketOptions = {}): WebSocketHook => {
const {
reconnectInterval = 3000,
maxReconnectAttempts = 0,
onOpen,
onClose,
onError,
onMessage
} = options;
const [readyState, setReadyState] = useState<number>(WebSocket.CLOSED);
const [isConnected, setIsConnected] = useState<boolean>(false);
const wsRef = useRef<WebSocket | null>(null);
const reconnectAttemptsRef = useRef<number>(0);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const urlRef = useRef<string>('');
// 清理重连定时器
const clearReconnectTimeout = useCallback(() => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
}, []);
// 断开连接
const disconnect = useCallback(() => {
clearReconnectTimeout();
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
setReadyState(WebSocket.CLOSED);
setIsConnected(false);
}, [clearReconnectTimeout]);
// 发送消息
const sendMessage = useCallback((message: string | object) => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
const messageStr = typeof message === 'string' ? message : JSON.stringify(message);
wsRef.current.send(messageStr);
} else {
console.warn('WebSocket is not connected. Cannot send message.');
}
}, []);
// 连接WebSocket
const connect = useCallback((url: string) => {
// 先断开现有连接
disconnect();
urlRef.current = url;
try {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = (event) => {
setReadyState(WebSocket.OPEN);
setIsConnected(true);
reconnectAttemptsRef.current = 0;
onOpen?.(event);
};
ws.onclose = (event) => {
setReadyState(WebSocket.CLOSED);
setIsConnected(false);
onClose?.(event);
// 处理重连
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
reconnectAttemptsRef.current++;
reconnectTimeoutRef.current = setTimeout(() => {
connect(url);
}, reconnectInterval);
}
};
ws.onerror = (event) => {
setIsConnected(false);
onError?.(event);
};
ws.onmessage = (event) => {
onMessage?.(event);
};
setReadyState(WebSocket.CONNECTING);
} catch (error) {
console.error('Failed to create WebSocket connection:', error);
setIsConnected(false);
setReadyState(WebSocket.CLOSED);
}
}, [disconnect, maxReconnectAttempts, onOpen, onClose, onError, onMessage, reconnectInterval]);
// 组件卸载时清理
useEffect(() => {
return () => {
disconnect();
};
}, [disconnect]);
return {
connect,
disconnect,
sendMessage,
readyState,
isConnected
};
};
export default useWebSocket;

@ -1,5 +1,5 @@
import React from 'react';
import { Button } from '@arco-design/web-react';
import { Button, Message } from '@arco-design/web-react';
import { IconSave, IconPlayArrow, IconCodeSquare, IconUndo, IconRedo } from '@arco-design/web-react/icon';
import { updateLogBarStatus } from '@/store/ideContainer';
import { useSelector, useDispatch } from 'react-redux';
@ -12,6 +12,8 @@ interface ActionBarProps {
onRedo?: () => void;
canUndo?: boolean;
canRedo?: boolean;
onRun?: (isRunning: boolean) => void;
isRunning?: boolean;
}
const ActionBar: React.FC<ActionBarProps> = ({
@ -19,7 +21,9 @@ const ActionBar: React.FC<ActionBarProps> = ({
onUndo,
onRedo,
canUndo = false,
canRedo = false
canRedo = false,
onRun,
isRunning = false
}) => {
const { logBarStatus } = useSelector((state) => state.ideContainer);
const dispatch = useDispatch();
@ -28,6 +32,10 @@ const ActionBar: React.FC<ActionBarProps> = ({
dispatch(updateLogBarStatus(!logBarStatus));
};
const handleRun = () => {
onRun?.(!isRunning);
};
return (
<div className="action-bar">
<Button onClick={onSave} type="primary" shape="round" icon={<IconSave />}></Button>
@ -36,9 +44,11 @@ const ActionBar: React.FC<ActionBarProps> = ({
type="outline"
shape="round"
icon={<IconPlayArrow />}
onClick={() => handleRun()}
style={{ padding: '0 8px', backgroundColor: '#fff' }}
status={isRunning ? 'danger' : undefined}
>
{isRunning ? '停止' : '运行'}
</Button>
<Button
type="outline"

@ -38,9 +38,11 @@ import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
import { setMainFlow } from '@/api/appRes';
import { getUserToken } from '@/api/user';
import { Message } from '@arco-design/web-react';
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
import { HistoryProvider, useHistory } from './components/historyContext';
import useWebSocket from '@/hooks/useWebSocket';
const edgeTypes: EdgeTypes = {
custom: CustomEdge
@ -769,6 +771,54 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
}
}, [nodes, edges]);
// 添加运行状态
const [isRunning, setIsRunning] = useState(false);
// 初始化WebSocket hook
const ws = useWebSocket({
onOpen: () => {
console.log('WebSocket连接已建立');
Message.success('运行已启动');
},
onClose: () => {
console.log('WebSocket连接已关闭');
setIsRunning(false);
Message.info('运行已停止');
},
onError: (event) => {
console.error('WebSocket错误:', event);
setIsRunning(false);
Message.error('运行连接出错');
},
onMessage: (event) => {
console.log('收到WebSocket消息:', event.data);
// 这里可以处理从后端收到的消息,例如日志更新等
}
});
// 修改运行处理函数
const handleRun = useCallback(async (running: boolean) => {
if (running) {
// 启动运行
const res = await getUserToken();
const token = res.data;
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
let wsApi = `${protocol}://${window.location.host}/ws/v1/bpms-runtime`;
if (window.location.host.includes('localhost')) {
// WS_API = `wss://${host}/ws/v1/bpms-runtime`;
wsApi = `ws://api.myserver.com:4121/ws/v1/bpms-runtime`;
}
const uri = `${wsApi}?x-auth0-token=${token}`;
ws.connect(uri);
setIsRunning(true);
}
else {
// 停止运行
ws.disconnect();
setIsRunning(false);
}
}, [initialData?.id, ws]);
if (!historyInitialized) {
return <div>Loading...</div>;
}
@ -833,6 +883,8 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
addNodeOnPane={addNodeOnPane}
handleAddNode={handleAddNode}
saveFlowDataToServer={saveFlowDataToServer}
handleRun={handleRun}
isRunning={isRunning}
/>
</HistoryProvider>
);
@ -886,7 +938,9 @@ const FlowEditorContent: React.FC<any> = (props) => {
addNodeOnEdge,
addNodeOnPane,
handleAddNode,
saveFlowDataToServer
saveFlowDataToServer,
handleRun,
isRunning
} = props;
const { undo, redo, canUndo, canRedo } = useHistory();
@ -988,6 +1042,8 @@ const FlowEditorContent: React.FC<any> = (props) => {
onRedo={redo}
canUndo={canUndo}
canRedo={canRedo}
onRun={handleRun}
isRunning={isRunning}
></ActionBar>
</Panel>
<AlignmentGuides />

Loading…
Cancel
Save