diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..a5329a2 --- /dev/null +++ b/src/hooks/useWebSocket.ts @@ -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(WebSocket.CLOSED); + const [isConnected, setIsConnected] = useState(false); + const wsRef = useRef(null); + const reconnectAttemptsRef = useRef(0); + const reconnectTimeoutRef = useRef(null); + const urlRef = useRef(''); + + // 清理重连定时器 + 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; \ No newline at end of file diff --git a/src/pages/flowEditor/components/actionBar.tsx b/src/pages/flowEditor/components/actionBar.tsx index 6088f08..0954743 100644 --- a/src/pages/flowEditor/components/actionBar.tsx +++ b/src/pages/flowEditor/components/actionBar.tsx @@ -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 = ({ @@ -19,7 +21,9 @@ const ActionBar: React.FC = ({ 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 = ({ dispatch(updateLogBarStatus(!logBarStatus)); }; + const handleRun = () => { + onRun?.(!isRunning); + }; + return (
@@ -36,9 +44,11 @@ const ActionBar: React.FC = ({ type="outline" shape="round" icon={} + onClick={() => handleRun()} style={{ padding: '0 8px', backgroundColor: '#fff' }} + status={isRunning ? 'danger' : undefined} > - 运行 + {isRunning ? '停止' : '运行'}