/** * 工作流协作 Composable * 支持多人实时协作编辑工作流 */ import { ref, onUnmounted } from 'vue' import { ElMessage } from 'element-plus' import { useUserStore } from '@/stores/user' import api from '@/api' export interface CollaborationUser { user_id: string username: string joined_at: string color: string } export interface CollaborationOperation { type: string user_id: string username: string data?: any timestamp?: string } export interface CollaborationMessage { type: string workflow_id?: string current_user?: CollaborationUser online_users?: CollaborationUser[] user?: CollaborationUser user_id?: string operation?: CollaborationOperation timestamp?: string } export function useCollaboration(workflowId: string) { const connected = ref(false) const onlineUsers = ref([]) const currentUser = ref(null) const ws = ref(null) let heartbeatInterval: number | null = null let reconnectTimeout: number | null = null const reconnectAttempts = ref(0) const maxReconnectAttempts = 5 // 操作监听器 type OperationHandler = (operation: CollaborationOperation) => void const operationHandlers = new Set() // 获取WebSocket URL const getWebSocketUrl = () => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const hostname = window.location.hostname const port = hostname === 'localhost' || hostname === '127.0.0.1' ? '8037' : '8037' const userStore = useUserStore() const token = userStore.token if (!token) { throw new Error('未登录,无法建立协作连接') } return `${protocol}//${hostname}:${port}/api/v1/collaboration/ws/workflows/${workflowId}?token=${token}` } // 连接WebSocket const connect = () => { if (ws.value && ws.value.readyState === WebSocket.OPEN) { return // 已经连接 } const wsUrl = getWebSocketUrl() console.log('[协作] 连接中:', wsUrl) try { ws.value = new WebSocket(wsUrl) ws.value.onopen = () => { console.log('[协作] 连接已建立') connected.value = true reconnectAttempts.value = 0 // 启动心跳 startHeartbeat() } ws.value.onmessage = (event) => { try { const message: CollaborationMessage = JSON.parse(event.data) handleMessage(message) } catch (e) { console.error('[协作] 解析消息失败:', e) } } ws.value.onerror = (err) => { console.error('[协作] 错误:', err) connected.value = false ElMessage.error('协作连接错误') } ws.value.onclose = (event) => { console.log('[协作] 连接已关闭', event.code, event.reason) connected.value = false stopHeartbeat() // 如果不是正常关闭,尝试重连 if (event.code !== 1000 && reconnectAttempts.value < maxReconnectAttempts) { reconnectAttempts.value++ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.value), 30000) console.log(`[协作] ${delay}ms后尝试重连 (${reconnectAttempts.value}/${maxReconnectAttempts})`) reconnectTimeout = window.setTimeout(() => { connect() }, delay) } } } catch (err) { console.error('[协作] 连接失败:', err) ElMessage.error('协作连接失败') } } // 处理消息 const handleMessage = (message: CollaborationMessage) => { console.log('[协作] 收到消息:', message) switch (message.type) { case 'collaboration_init': // 初始化消息 if (message.current_user) { currentUser.value = message.current_user } if (message.online_users) { onlineUsers.value = message.online_users } break case 'user_joined': // 用户加入 if (message.user) { onlineUsers.value.push(message.user) ElMessage.info(`${message.user.username} 加入了协作编辑`) } break case 'user_left': // 用户离开 if (message.user_id) { onlineUsers.value = onlineUsers.value.filter(u => u.user_id !== message.user_id) const user = onlineUsers.value.find(u => u.user_id === message.user_id) if (user) { ElMessage.info(`${user.username} 离开了协作编辑`) } } break case 'operation': // 工作流操作 if (message.operation) { // 触发所有注册的操作处理器 operationHandlers.forEach(handler => { try { handler(message.operation!) } catch (e) { console.error('[协作] 操作处理器执行失败:', e) } }) console.log('[协作] 收到操作:', message.operation) } break case 'pong': // 心跳响应 break case 'error': ElMessage.error(message.message || '协作错误') break } } // 发送操作 const sendOperation = (operation: Omit) => { if (!ws.value || ws.value.readyState !== WebSocket.OPEN) { console.warn('[协作] WebSocket未连接,无法发送操作') return } const fullOperation: CollaborationOperation = { ...operation, user_id: currentUser.value?.user_id || '', username: currentUser.value?.username || '', timestamp: new Date().toISOString() } const message = { type: 'operation', operation: fullOperation } try { ws.value.send(JSON.stringify(message)) } catch (e) { console.error('[协作] 发送操作失败:', e) } } // 启动心跳 const startHeartbeat = () => { heartbeatInterval = window.setInterval(() => { if (ws.value && ws.value.readyState === WebSocket.OPEN) { ws.value.send(JSON.stringify({ type: 'ping' })) } }, 30000) // 每30秒发送一次心跳 } // 停止心跳 const stopHeartbeat = () => { if (heartbeatInterval) { clearInterval(heartbeatInterval) heartbeatInterval = null } if (reconnectTimeout) { clearTimeout(reconnectTimeout) reconnectTimeout = null } } // 断开连接 const disconnect = () => { stopHeartbeat() if (ws.value) { ws.value.close(1000, '正常关闭') ws.value = null } connected.value = false onlineUsers.value = [] currentUser.value = null } // 获取在线用户列表 const fetchOnlineUsers = async () => { try { const response = await api.get(`/api/v1/collaboration/workflows/${workflowId}/users`) if (response.data.online_users) { onlineUsers.value = response.data.online_users } } catch (e) { console.error('[协作] 获取在线用户失败:', e) } } // 注册操作监听器 const onOperation = (handler: OperationHandler) => { operationHandlers.add(handler) // 返回取消注册函数 return () => { operationHandlers.delete(handler) } } // 取消注册操作监听器 const offOperation = (handler: OperationHandler) => { operationHandlers.delete(handler) } // 清理 onUnmounted(() => { disconnect() operationHandlers.clear() }) return { connected, onlineUsers, currentUser, connect, disconnect, sendOperation, fetchOnlineUsers, onOperation, offOperation } }