import { defineStore } from "pinia" import { ref, computed, watch } from "vue" import { chatDB } from "@/utils/db" import { useWebRTC } from "@/composables/useWebRTC" import { useUserStore } from "@/stores/user" import { sendCallSignal, callAPI } from "@/utils/request.js" export const useChatStore = defineStore("chat", () => { const friends = ref([]) const currentFriend = ref(null) const messages = ref([]) const connectionStatus = ref("disconnected") const socket = ref(null) // 通话相关状态增强 const currentCall = ref(null) const incomingCall = ref(null) const callStatus = ref("idle") // idle, calling, ringing, connecting, ongoing, ended, disconnected, hangup const callError = ref(null) const callConnectionStatus = ref("") const callTimeout = ref(null) const webrtc = useWebRTC() const userStore = useUserStore() // 计算属性 const isConnected = computed(() => connectionStatus.value === "connected") const isInCall = computed(() => ["calling", "ringing", "connecting", "ongoing"].includes(callStatus.value)) const isCalling = computed(() => callStatus.value === "calling") const isRinging = computed(() => callStatus.value === "ringing") const isConnecting = computed(() => callStatus.value === "connecting") const isOngoingCall = computed(() => callStatus.value === "ongoing") const isDisconnected = computed(() => callStatus.value === "disconnected") const isHangup = computed(() => callStatus.value === "hangup") // 监听通话状态变化 watch( () => callStatus.value, (newStatus) => { console.log("通话状态变化:", newStatus) }, ) // 重置所有通话状态 const resetCallState = () => { clearCallTimeout() callStatus.value = "idle" callError.value = null callConnectionStatus.value = "" currentCall.value = null incomingCall.value = null } // 清除通话超时 const clearCallTimeout = () => { if (callTimeout.value) { clearTimeout(callTimeout.value) callTimeout.value = null } } // 设置通话超时 const setCallTimeout = (callback, duration = 30000) => { clearCallTimeout() callTimeout.value = setTimeout(() => { callback() clearCallTimeout() }, duration) } // 发送通话信令 const sendCallSignalInternal = async (signal) => { try { console.log("发送通话信令:", signal) if (!userStore.currentUser?.id) { throw new Error("用户未登录") } if (!signal.receiver_user_id) { throw new Error("缺少接收方用户ID") } if (!signal.call_id) { throw new Error("缺少通话ID") } if (!signal.call_status) { throw new Error("缺少通话状态") } const requestData = { sender_user_id: userStore.currentUser.id, receiver_user_id: signal.receiver_user_id, message_type: signal.call_type || (signal.call_status === "invite" ? 6 : 7), message_content: JSON.stringify(signal.data || {}), call_id: signal.call_id, call_status: signal.call_status, } console.log("发送通话信令请求数据:", requestData) const response = await sendCallSignal(requestData) console.log("发送通话信令成功:", signal) return response } catch (error) { console.error("发送通话信令失败:", error) throw error } } // 初始化数据库 const initDB = async () => { try { await chatDB.init() console.log("数据库初始化成功") } catch (error) { console.error("数据库初始化失败:", error) } } // 加载好友列表 const loadFriends = async (presetUsers, currentUserId) => { try { let friendList = await chatDB.getFriends(currentUserId) if (friendList.length === 0) { friendList = presetUsers.filter((user) => user.id !== currentUserId) for (const friend of friendList) { await chatDB.saveFriend( { ...friend, lastMessage: "", unreadCount: 0, }, currentUserId, ) } } friends.value = friendList } catch (error) { console.error("加载好友列表失败:", error) const friendList = presetUsers.filter((user) => user.id !== currentUserId) friendList.forEach((friend) => { friend.lastMessage = "" friend.unreadCount = 0 }) friends.value = friendList } } // 设置当前好友 const setCurrentFriend = (friend) => { currentFriend.value = friend if (friend) { friend.unreadCount = 0 } } // 切换当前聊天好友 const switchFriend = async (friend, currentUserId) => { currentFriend.value = friend friend.unreadCount = 0 await chatDB.clearUnreadCount(friend.id) try { const chatHistory = await chatDB.getChatHistory(currentUserId, friend.id) messages.value = chatHistory.sort((a, b) => a.timestamp - b.timestamp) } catch (error) { console.error("加载聊天记录失败:", error) messages.value = [] } } // 添加消息 const addMessage = async (message, currentUserId) => { messages.value.push(message) const friend = friends.value.find((f) => f.id === message.senderId || f.id === currentFriend.value?.id) if (friend) { let lastMessage = message.content if (message.type === "image") lastMessage = "[图片]" if (message.type === "video") lastMessage = "[视频]" if (message.type === "audio") lastMessage = "[语音]" if (message.type === "video-call" || message.type === "audio-call") lastMessage = "[通话]" friend.lastMessage = lastMessage.length > 20 ? lastMessage.substring(0, 20) + "..." : lastMessage await chatDB.updateFriendLastMessage(friend.id, currentUserId, friend.lastMessage, 0) } if (currentFriend.value && friend && friend.id === currentFriend.value.id) { try { await chatDB.saveMessage(message, currentUserId, currentFriend.value.id) } catch (error) { console.error("保存消息失败:", error) } } } // 处理接收到的消息 const handleIncomingMessage = async (messageData, currentUserId) => { if (messageData.isFromHttp) { console.log("忽略来自HTTP的消息,避免循环") return } const senderId = messageData.sender_user_id const receiverId = messageData.receiver_user_id if (receiverId !== currentUserId) return if (messageData.call_id || messageData.call_status) { handleCallSignal(messageData) return } const newMessage = { type: getMessageType(messageData.message_type), content: messageData.content, time: new Date().toLocaleTimeString().slice(0, 5), senderId: senderId, read: false, duration: messageData.duration || 0, timestamp: Date.now(), } try { await chatDB.saveMessage(newMessage, currentUserId, senderId) if (currentFriend.value && senderId == currentFriend.value.id) { newMessage.read = true messages.value.push(newMessage) } else { const friend = friends.value.find((f) => f.id == senderId) if (friend) { friend.unreadCount = (friend.unreadCount || 0) + 1 } } const friend = friends.value.find((f) => f.id == senderId) if (friend) { let lastMessage = newMessage.content if (newMessage.type === "image") lastMessage = "[图片]" if (newMessage.type === "video") lastMessage = "[视频]" if (newMessage.type === "audio") lastMessage = "[语音]" if (newMessage.type === "video-call" || newMessage.type === "audio-call") lastMessage = "[通话]" friend.lastMessage = lastMessage await chatDB.updateFriendLastMessage(friend.id, currentUserId, lastMessage, friend.unreadCount) } } catch (error) { console.error("处理接收消息失败:", error) } } // 根据消息类型编号获取类型字符串 const getMessageType = (type) => { const types = [ "text", "image", "audio", "video", "prescription", "medical-record", "video-call", "audio-call", "file", ] return types[type] || "text" } // 处理通话信令 const handleCallSignal = (data) => { console.log("处理通话信令:", data) console.log("通话状态:", data.call_status) switch (data.call_status) { case "invite": handleIncomingCall(data) break case "accepted": handleCallAccepted(data) break case "rejected": handleCallRejected(data) break case "ended": handleCallEnded(data) break case "offer": handleOffer(data) break case "answer": handleAnswer(data) break case "candidate": handleIceCandidate(data) break case "no-answer": handleCallNoAnswer(data) break case "connecting": handleCallConnecting(data) break case "busy": handleCallBusy(data) break case "hangup": handleCallHangup(data) break case "disconnected": handleCallDisconnected(data) break case "terminated": handleCallTerminated(data) break case "failed": handleCallFailed(data) break } } // 处理Offer信令 const handleOffer = async (data) => { if (incomingCall.value && incomingCall.value.callId === data.call_id) { if (callStatus.value === "ongoing") { console.log("通话已建立,忽略重复的 Offer") return } try { const offer = JSON.parse(data.content || data.message_content || "{}") console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") console.log("📥 收到对方 OFFER 信令:") console.log("📋 Offer 类型:", offer.type) console.log("📋 Offer SDP (前200字符):", offer.sdp ? offer.sdp.substring(0, 200) + "..." : "无SDP") console.log("📋 通话ID:", data.call_id) console.log("📋 发送方:", data.sender_user_id) console.log("📋 完整 Offer 对象:", offer) console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") if (!webrtc.peerConnection.value) { console.log("创建 WebRTC 连接以处理 Offer") webrtc.createPeerConnection(data.call_id, data.sender_user_id) } const answer = await webrtc.createAnswer(offer, data.call_id, data.sender_user_id) console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") console.log("📤 准备发送 ANSWER 信令:") console.log("📋 Answer 类型:", answer.type) console.log("📋 Answer SDP (前200字符):", answer.sdp ? answer.sdp.substring(0, 200) + "..." : "无SDP") console.log("📋 通话ID:", data.call_id) console.log("📋 接收方:", data.sender_user_id) console.log("📋 完整 Answer 对象:", answer) console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") await callAPI.sendAnswer(userStore.currentUser.id, data.sender_user_id, data.call_id, answer, data.message_type) console.log("✅ 发送Answer成功") } catch (error) { console.error("处理Offer失败:", error) rejectCall("failed") } } } // 处理Answer信令 const handleAnswer = async (data) => { if (currentCall.value && currentCall.value.callId === data.call_id) { if (callStatus.value === "ongoing") { console.log("通话已建立,忽略重复的 Answer") return } try { const answer = JSON.parse(data.content || data.message_content || "{}") console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") console.log("📥 收到对方 ANSWER 信令:") console.log("📋 Answer 类型:", answer.type) console.log("📋 Answer SDP (前200字符):", answer.sdp ? answer.sdp.substring(0, 200) + "..." : "无SDP") console.log("📋 通话ID:", data.call_id) console.log("📋 发送方:", data.sender_user_id) console.log("📋 完整 Answer 对象:", answer) console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") await webrtc.setRemoteAnswer(answer) callStatus.value = "connecting" callConnectionStatus.value = "正在建立连接..." console.log("✅ Answer处理成功,开始建立连接") } catch (error) { console.error("处理Answer失败:", error) endCall("failed") } } } // 处理连接中状态 const handleCallConnecting = (data) => { callConnectionStatus.value = "正在连接..." if (currentCall.value?.callId === data.call_id) { callStatus.value = "connecting" } } // 处理通话失败 const handleCallFailed = (data) => { if ( (currentCall.value && currentCall.value.callId === data.call_id) || (incomingCall.value && incomingCall.value.callId === data.call_id) ) { callError.value = "failed" callConnectionStatus.value = "连接失败" const peerId = currentCall.value?.peerId || incomingCall.value?.callerId const friend = friends.value.find((f) => f.id === peerId) if (friend) { const callMessage = { type: currentCall.value?.callType === "video" || incomingCall.value?.callType === "video" ? "video-call" : "audio-call", content: "[连接失败]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: peerId, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = "[连接失败]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[连接失败]", friend.unreadCount) } setTimeout(() => { resetCallState() }, 5000) } } // 处理来电 const handleIncomingCall = (data) => { if (isInCall.value) { console.log("当前正在通话中,发送忙线状态") callAPI.sendBusy(userStore.currentUser.id, data.sender_user_id, data.call_id, data.message_type) const friend = friends.value.find((f) => f.id === data.sender_user_id) if (friend) { friend.lastMessage = "[未接来电]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount) const callMessage = { type: data.message_type === 6 ? "video-call" : "audio-call", content: "[未接来电]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: data.sender_user_id, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) } return } resetCallState() incomingCall.value = { callId: data.call_id, callerId: data.sender_user_id, callType: data.message_type === 6 ? "video" : "audio", content: data.content, } callStatus.value = "ringing" callError.value = null callConnectionStatus.value = "收到来电" console.log("收到来电:", incomingCall.value) setCallTimeout(() => { if (callStatus.value === "ringing" && incomingCall.value) { console.log("来电超时,自动拒绝") rejectCall("no-answer") } }, 30000) } // 处理通话接受 const handleCallAccepted = async (data) => { if (currentCall.value && currentCall.value.callId === data.call_id) { if (callStatus.value === "connecting" || callStatus.value === "ongoing") { console.log("通话已在连接中,忽略重复的 accepted 信令") return } callStatus.value = "connecting" callError.value = null callConnectionStatus.value = "对方已接听,正在连接..." console.log("通话已被接受,开始交换信令") // 显示友好提示 setTimeout(() => { if (callStatus.value === "connecting") { callConnectionStatus.value = "对方已接听,正在建立连接..." } }, 1000) try { if (!webrtc.localStream.value) { console.log("获取本地媒体用于创建Offer") await webrtc.getLocalMedia({ audio: true, video: currentCall.value.callType === "video", }) } const offer = await webrtc.createOffer(currentCall.value.callId, currentCall.value.peerId) console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") console.log("📤 准备发送 OFFER 信令:") console.log("📋 Offer 类型:", offer.type) console.log("📋 Offer SDP (前200字符):", offer.sdp ? offer.sdp.substring(0, 200) + "..." : "无SDP") console.log("📋 通话ID:", currentCall.value.callId) console.log("📋 接收方:", currentCall.value.peerId) console.log("📋 完整 Offer 对象:", offer) console.log("🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯🎯") await callAPI.sendOffer( userStore.currentUser.id, currentCall.value.peerId, currentCall.value.callId, offer, currentCall.value.callType === "video" ? 6 : 7, ) console.log("✅ 发送Offer成功") } catch (error) { console.error("创建Offer失败:", error) endCall("failed") } } } // 处理通话拒绝 const handleCallRejected = (data) => { if (currentCall.value && currentCall.value.callId === data.call_id) { resetCallState() callConnectionStatus.value = "对方已拒绝" console.log("通话已被拒绝") const friend = friends.value.find((f) => f.id === currentCall.value.peerId) if (friend) { const callMessage = { type: currentCall.value.callType === "video" ? "video-call" : "audio-call", content: "[对方已拒绝]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: userStore.currentUser?.id, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = "[对方已拒绝]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已拒绝]", friend.unreadCount) } setTimeout(() => { callConnectionStatus.value = "" }, 5000) } } // 处理通话结束 const handleCallEnded = (data) => { if ( (currentCall.value && currentCall.value.callId === data.call_id) || (incomingCall.value && incomingCall.value.callId === data.call_id) ) { const peerId = currentCall.value?.peerId || incomingCall.value?.callerId const friend = friends.value.find((f) => f.id === peerId) if (friend) { const duration = callStatus.value === "ongoing" ? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0 const callMessage = { type: currentCall.value?.callType === "video" || incomingCall.value?.callType === "video" ? "video-call" : "audio-call", content: callStatus.value === "ongoing" ? `[${currentCall.value?.callType === "video" ? "视频" : "语音"}通话] ${Math.floor(duration / 60)}分${duration % 60}秒` : "[通话结束]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: peerId, read: true, timestamp: Date.now(), duration: duration, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = callMessage.content chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, callMessage.content, friend.unreadCount) } resetCallState() callConnectionStatus.value = "通话已结束" console.log("通话已结束") setTimeout(() => { callConnectionStatus.value = "" }, 5000) } } // 处理ICE候选 const handleIceCandidate = (data) => { if ( (currentCall.value && currentCall.value.callId === data.call_id) || (incomingCall.value && incomingCall.value.callId === data.call_id) ) { try { const candidate = JSON.parse(data.content || data.message_content || "{}") console.log("收到ICE候选:", candidate) webrtc.addIceCandidate(candidate) } catch (error) { console.error("处理ICE候选失败:", error) } } } // 处理无人接听 const handleCallNoAnswer = (data) => { if (currentCall.value && currentCall.value.callId === data.call_id) { const friend = friends.value.find((f) => f.id === currentCall.value.peerId) if (friend) { const callMessage = { type: currentCall.value.callType === "video" ? "video-call" : "audio-call", content: "[未接来电]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: userStore.currentUser?.id, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = "[未接来电]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount) } resetCallState() callConnectionStatus.value = "对方无人接听" console.log("对方无人接听") setTimeout(() => { callConnectionStatus.value = "" }, 5000) } } // 处理对方忙线 const handleCallBusy = (data) => { if (currentCall.value && currentCall.value.callId === data.call_id) { const friend = friends.value.find((f) => f.id === currentCall.value.peerId) if (friend) { const callMessage = { type: currentCall.value.callType === "video" ? "video-call" : "audio-call", content: "[对方忙线]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: userStore.currentUser?.id, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = "[对方忙线]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方忙线]", friend.unreadCount) } resetCallState() callConnectionStatus.value = "对方忙线中" console.log("对方忙线中") setTimeout(() => { callConnectionStatus.value = "" }, 5000) } } // 处理对方挂断 const handleCallHangup = (data) => { const callId = data?.call_id || currentCall.value?.callId || incomingCall.value?.callId const peerId = data?.sender_user_id || currentCall.value?.peerId || incomingCall.value?.callerId if (callId && peerId) { const friend = friends.value.find((f) => f.id === peerId) if (friend) { const duration = callStatus.value === "ongoing" ? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0 const callMessage = { type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call", content: duration > 0 ? `[对方已挂断] ${Math.floor(duration / 60)}分${duration % 60}秒` : "[对方已挂断]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: peerId, read: true, timestamp: Date.now(), duration: duration, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = callMessage.content chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount) } callStatus.value = "hangup" callConnectionStatus.value = "对方已挂断" console.log("对方已挂断通话") setTimeout(() => { resetCallState() }, 3000) } } // 处理对方掉线 const handleCallDisconnected = (data) => { const callId = data?.call_id || currentCall.value?.callId || incomingCall.value?.callId const peerId = data?.sender_user_id || currentCall.value?.peerId || incomingCall.value?.callerId if (callId && peerId) { const friend = friends.value.find((f) => f.id === peerId) if (friend) { const duration = callStatus.value === "ongoing" ? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0 const callMessage = { type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call", content: duration > 0 ? `[网络中断] ${Math.floor(duration / 60)}分${duration % 60}秒` : "[网络中断]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: peerId, read: true, timestamp: Date.now(), duration: duration, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = callMessage.content chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount) } callStatus.value = "disconnected" callConnectionStatus.value = "网络连接已断开" console.log("网络连接已断开") setTimeout(() => { resetCallState() }, 3000) } } // 处理对方终止呼叫 const handleCallTerminated = (data) => { const callId = data.call_id const peerId = data.sender_user_id if ( (currentCall.value && currentCall.value.callId === callId) || (incomingCall.value && incomingCall.value.callId === callId) ) { const friend = friends.value.find((f) => f.id === peerId) if (friend) { const callMessage = { type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call", content: "[对方已终止呼叫]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: peerId, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = "[对方已终止呼叫]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已终止呼叫]", friend.unreadCount) } resetCallState() callConnectionStatus.value = "对方已终止呼叫" console.log("对方已终止呼叫") setTimeout(() => { callConnectionStatus.value = "" }, 5000) } } // 发起通话 const startCall = async (peerId, type) => { if (isInCall.value) { alert("您正在通话中,请先结束当前通话。") return } const friend = friends.value.find((f) => f.id === peerId) if (!friend) { alert("好友信息无效") return } if (!userStore.currentUser?.id) { alert("用户未登录,无法发起通话") return } resetCallState() const callId = Date.now().toString() + Math.random().toString(36).substr(2, 9) currentCall.value = { callId: callId, peerId: peerId, callType: type, startTime: Date.now(), status: "calling", } callStatus.value = "calling" callConnectionStatus.value = "等待对方接听..." // 立即获取本地媒体并显示 try { console.log("发起通话,立即获取本地媒体...") await webrtc.getLocalMedia({ audio: true, video: type === "video", }) console.log("本地媒体获取成功,可以显示自己的摄像头") } catch (error) { console.warn("获取本地媒体失败,但继续通话流程:", error) } // 设置30秒超时 setCallTimeout(() => { if (callStatus.value === "calling") { resetCallState() callConnectionStatus.value = "对方无人接听" console.log("呼叫超时,对方无人接听") callAPI.sendNoAnswer(userStore.currentUser.id, peerId, callId, type === "video" ? 6 : 7) friend.lastMessage = "[未接来电]" chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount) const callMessage = { type: type === "video" ? "video-call" : "audio-call", content: "[未接来电]", time: new Date().toLocaleTimeString().slice(0, 5), senderId: userStore.currentUser?.id, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) setTimeout(() => { callConnectionStatus.value = "" }, 5000) } }, 30000) // 发送通话请求 try { console.log("准备发送通话邀请:", { senderUserId: userStore.currentUser.id, receiverUserId: peerId, callId: callId, callType: type === "video" ? 6 : 7, }) await callAPI.invite(userStore.currentUser.id, peerId, callId, type === "video" ? 6 : 7) console.log("发起通话成功:", currentCall.value) } catch (error) { console.error("发起通话失败:", error) resetCallState() let errorMessage = "发起通话失败" if (error.response?.status === 400) { errorMessage = "请求参数错误,请检查网络连接" } else if (error.response?.status === 500) { errorMessage = "服务器错误,请稍后重试" } else if (error.message) { errorMessage = error.message } alert(errorMessage) } } // 接听来电 const acceptCall = async () => { if (!incomingCall.value) { console.log("没有来电,无法接听") return } if (callStatus.value === "connecting" || callStatus.value === "ongoing") { console.log("通话已在进行中,忽略重复接听") return } clearCallTimeout() console.log("开始接听通话:", incomingCall.value) callStatus.value = "connecting" callConnectionStatus.value = "正在连接..." try { // 获取本地媒体 try { await webrtc.getLocalMedia({ audio: true, video: incomingCall.value.callType === "video", }) console.log("接听时获取本地媒体成功") } catch (mediaError) { console.warn("接听时获取本地媒体失败,但继续通话流程:", mediaError) } // 发送接受信号 await callAPI.accept( userStore.currentUser.id, incomingCall.value.callerId, incomingCall.value.callId, incomingCall.value.callType === "video" ? 6 : 7, ) currentCall.value = { callId: incomingCall.value.callId, peerId: incomingCall.value.callerId, callType: incomingCall.value.callType, startTime: Date.now(), status: "connecting", } console.log("接听通话成功,等待信令交换...") } catch (error) { console.error("接听通话失败:", error) rejectCall("failed") } } // 拒绝来电 const rejectCall = async (reason = "rejected") => { if (!incomingCall.value) { console.log("没有来电,无法拒绝") return } if (callStatus.value === "connecting" || callStatus.value === "ongoing") { console.log("通话已在进行中,无法拒绝") return } console.log("开始拒绝通话:", reason) try { if (reason === "rejected") { await callAPI.reject( userStore.currentUser.id, incomingCall.value.callerId, incomingCall.value.callId, incomingCall.value.callType === "video" ? 6 : 7, ) } else { await callAPI.sendFailed( userStore.currentUser.id, incomingCall.value.callerId, incomingCall.value.callId, incomingCall.value.callType === "video" ? 6 : 7, ) } const friend = friends.value.find((f) => f.id === incomingCall.value.callerId) if (friend) { const content = reason === "busy" ? "[对方忙线]" : reason === "no-answer" ? "[未接来电]" : "[已拒绝]" const callMessage = { type: incomingCall.value.callType === "video" ? "video-call" : "audio-call", content: content, time: new Date().toLocaleTimeString().slice(0, 5), senderId: userStore.currentUser?.id, read: true, timestamp: Date.now(), duration: 0, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = content chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, content, friend.unreadCount) } resetCallState() callConnectionStatus.value = reason === "busy" ? "对方忙线" : reason === "no-answer" ? "无人接听" : "已拒绝" setTimeout(() => { callConnectionStatus.value = "" }, 5000) console.log("拒绝通话成功") } catch (error) { console.error("拒绝通话失败:", error) } } // 结束通话 const endCall = async (reason = "ended") => { const callId = currentCall.value?.callId || incomingCall.value?.callId const peerId = currentCall.value?.peerId || incomingCall.value?.callerId const callTypeVal = currentCall.value?.callType || incomingCall.value?.callType if (!callId || !peerId) { resetCallState() return } try { await callAPI.end(userStore.currentUser.id, peerId, callId, callTypeVal === "video" ? 6 : 7) const friend = friends.value.find((f) => f.id === peerId) if (friend) { const duration = callStatus.value === "ongoing" ? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000) : 0 let content = "" if (reason === "no-answer") { content = "[未接来电]" } else if (duration > 0) { content = `[${callTypeVal === "video" ? "视频" : "语音"}通话] ${Math.floor(duration / 60)}分${duration % 60}秒` } else { content = "[通话结束]" } const callMessage = { type: callTypeVal === "video" ? "video-call" : "audio-call", content: content, time: new Date().toLocaleTimeString().slice(0, 5), senderId: userStore.currentUser?.id, read: true, timestamp: Date.now(), duration: duration, } chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id) friend.lastMessage = content chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, content, friend.unreadCount) } resetCallState() callConnectionStatus.value = reason === "no-answer" ? "对方无人接听" : "通话结束" setTimeout(() => { callConnectionStatus.value = "" }, 5000) console.log("结束通话成功") } catch (error) { console.error("结束通话失败:", error) resetCallState() } } return { friends, currentFriend, messages, connectionStatus, socket, currentCall, incomingCall, callStatus, callError, callConnectionStatus, isConnected, isInCall, isCalling, isRinging, isConnecting, isOngoingCall, isDisconnected, isHangup, initDB, loadFriends, setCurrentFriend, switchFriend, addMessage, handleIncomingMessage, handleCallSignal, startCall, acceptCall, rejectCall, endCall, resetCallState, sendCallSignal: sendCallSignalInternal, clearCallTimeout, } })