Files
nl-im-pc/src/stores/chat.js

1039 lines
32 KiB
JavaScript
Raw Normal View History

2025-07-02 16:54:52 +08:00
import { defineStore } from "pinia"
import { ref, computed, watch } from "vue"
import { chatDB } from "@/utils/db"
2025-07-05 16:09:14 +08:00
import { useWebRTC } from "@/composables/useWebRTC"
import { useUserStore } from "@/stores/user"
2025-07-02 16:54:52 +08:00
export const useChatStore = defineStore("chat", () => {
const friends = ref([])
const currentFriend = ref(null)
const messages = ref([])
const connectionStatus = ref("disconnected")
const socket = ref(null)
// 通话相关状态增强
2025-07-05 16:09:14 +08:00
const currentCall = ref(null)
const incomingCall = ref(null)
const callStatus = ref("idle") // idle, calling, ringing, connecting, ongoing, ended, disconnected, hangup
const callError = ref(null) // 错误状态:'rejected', 'no-answer', 'busy', 'failed'
const callConnectionStatus = ref("") // 连接状态描述
const callTimeout = ref(null) // 通话超时计时器
2025-07-05 16:09:14 +08:00
const webrtc = useWebRTC()
const userStore = useUserStore()
2025-07-02 16:54:52 +08:00
// 计算属性
const isConnected = computed(() => connectionStatus.value === "connected")
2025-07-09 08:18:59 +08:00
const isInCall = computed(() => ["calling", "ringing", "connecting", "ongoing"].includes(callStatus.value))
2025-07-05 16:09:14 +08:00
const isCalling = computed(() => callStatus.value === "calling")
const isRinging = computed(() => callStatus.value === "ringing")
const isConnecting = computed(() => callStatus.value === "connecting")
2025-07-05 16:09:14 +08:00
const isOngoingCall = computed(() => callStatus.value === "ongoing")
const isDisconnected = computed(() => callStatus.value === "disconnected")
const isHangup = computed(() => callStatus.value === "hangup")
// 监听通话状态变化
2025-07-09 08:18:59 +08:00
watch(
() => callStatus.value,
(newStatus) => {
console.log("通话状态变化:", newStatus)
switch (newStatus) {
case "disconnected":
handleCallDisconnected()
break
case "hangup":
handleCallHangup()
break
}
},
)
// 重置所有通话状态
const resetCallState = () => {
clearCallTimeout()
callStatus.value = "idle"
callError.value = null
callConnectionStatus.value = ""
currentCall.value = null
incomingCall.value = null
2025-07-09 08:18:59 +08:00
webrtc.closeConnection()
}
// 清除通话超时
const clearCallTimeout = () => {
if (callTimeout.value) {
clearTimeout(callTimeout.value)
callTimeout.value = null
}
}
// 设置通话超时
const setCallTimeout = (callback, duration = 30000) => {
clearCallTimeout()
callTimeout.value = setTimeout(() => {
callback()
clearCallTimeout()
}, duration)
}
// 发送通话信令
const sendCallSignal = (signal) => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
const message = {
2025-07-09 08:18:59 +08:00
call_status: signal.call_status,
call_type: signal.call_type || (signal.receiver_user_id ? 6 : 7), // 默认视频通话
call_id: signal.call_id,
caller_id: userStore.currentUser.id,
callee_id: signal.receiver_user_id,
data: JSON.stringify(signal.offer || signal.answer || signal.candidate || {}),
}
socket.value.send(JSON.stringify(message))
2025-07-09 08:18:59 +08:00
console.log("发送通话信令:", message)
} else {
console.error("WebSocket未连接无法发送通话信令")
}
}
2025-07-02 16:54:52 +08:00
// 初始化数据库
const initDB = async () => {
try {
await chatDB.init()
console.log("数据库初始化成功")
} catch (error) {
console.error("数据库初始化失败:", error)
}
2025-07-02 16:54:52 +08:00
}
// 加载好友列表
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,
},
2025-07-09 08:18:59 +08:00
currentUserId,
)
}
2025-07-02 16:54:52 +08:00
}
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
}
2025-07-02 16:54:52 +08:00
}
2025-07-05 16:09:14 +08:00
// 设置当前好友
const setCurrentFriend = (friend) => {
2025-07-02 16:54:52 +08:00
currentFriend.value = friend
// 清除未读消息计数
if (friend) {
friend.unreadCount = 0
}
}
// 切换当前聊天好友
const switchFriend = async (friend, currentUserId) => {
currentFriend.value = friend
2025-07-02 16:54:52 +08:00
// 清空未读消息计数
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 = []
}
2025-07-02 16:54:52 +08:00
}
// 添加消息
const addMessage = async (message, currentUserId) => {
2025-07-02 16:54:52 +08:00
messages.value.push(message)
// 更新好友列表中的最后消息
2025-07-09 08:18:59 +08:00
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)
2025-07-02 16:54:52 +08:00
}
}
}
// 处理接收到的消息
const handleIncomingMessage = async (messageData, currentUserId) => {
// 添加来源检查 - 防止循环
if (messageData.isFromHttp) {
console.log("忽略来自HTTP的消息避免循环")
return
}
2025-07-02 16:54:52 +08:00
const senderId = messageData.sender_user_id
const receiverId = messageData.receiver_user_id
if (receiverId !== currentUserId) return
2025-07-05 16:09:14 +08:00
// 如果是通话信令,直接处理
if (messageData.call_id || messageData.call_status) {
handleCallSignal(messageData)
return
}
2025-07-02 16:54:52 +08:00
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(),
2025-07-02 16:54:52 +08:00
}
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
}
2025-07-02 16:54:52 +08:00
}
// 更新最后消息
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)
2025-07-02 16:54:52 +08:00
}
}
// 根据消息类型编号获取类型字符串
const getMessageType = (type) => {
const types = [
2025-07-09 08:18:59 +08:00
"text",
"image",
"audio",
"video",
"prescription",
"medical-record",
"video-call",
"audio-call",
"file",
]
2025-07-02 16:54:52 +08:00
return types[type] || "text"
}
2025-07-05 16:09:14 +08:00
// 处理通话信令
const handleCallSignal = (data) => {
console.log("处理通话信令:", data)
console.log("通话状态:", data.call_status)
2025-07-05 16:09:14 +08:00
// 根据通话状态处理
switch (data.call_status) {
case "invite":
handleIncomingCall(data)
break
case "accepted":
handleCallAccepted(data)
break
case "rejected":
handleCallRejected(data)
break
case "ended":
handleCallEnded(data)
break
2025-07-09 08:18:59 +08:00
case "offer":
handleOffer(data)
break
case "answer":
handleAnswer(data)
break
2025-07-05 16:09:14 +08:00
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
}
}
2025-07-09 08:18:59 +08:00
// 处理Offer信令
const handleOffer = async (data) => {
if (incomingCall.value && incomingCall.value.callId === data.call_id) {
try {
const offer = JSON.parse(data.data)
console.log("收到Offer:", offer)
// 设置远程Offer并创建Answer
await webrtc.setRemoteOffer(offer)
const answer = await webrtc.createAnswer(offer, data.call_id, data.caller_id)
console.log("发送Answer:", answer)
} catch (error) {
console.error("处理Offer失败:", error)
rejectCall("failed")
}
}
}
// 处理Answer信令
const handleAnswer = async (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
try {
const answer = JSON.parse(data.data)
console.log("收到Answer:", answer)
// 设置远程Answer
await webrtc.setRemoteAnswer(answer)
callStatus.value = "connecting"
callConnectionStatus.value = "正在建立连接..."
} 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) => {
2025-07-09 08:18:59 +08:00
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
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
const callMessage = {
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
friend.lastMessage = "[连接失败]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[连接失败]", friend.unreadCount)
}
// 5秒后清除状态
setTimeout(() => {
resetCallState()
}, 5000)
2025-07-05 16:09:14 +08:00
}
}
// 处理来电
const handleIncomingCall = (data) => {
// 只有在当前没有通话时才处理来电
if (isInCall.value) {
console.log("当前正在通话中,发送忙线状态")
2025-07-09 08:18:59 +08:00
sendCallSignal({
call_status: "busy",
call_id: data.call_id,
receiver_user_id: data.caller_id,
})
// 添加未接来电消息
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === data.caller_id)
if (friend) {
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
// 添加通话记录
const callMessage = {
2025-07-09 08:18:59 +08:00
type: data.call_type === 6 ? "video-call" : "audio-call",
content: "[未接来电]",
time: new Date().toLocaleTimeString().slice(0, 5),
2025-07-09 08:18:59 +08:00
senderId: data.caller_id,
read: true,
timestamp: Date.now(),
2025-07-09 08:18:59 +08:00
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
}
return
}
// 重置所有通话状态
2025-07-09 08:18:59 +08:00
resetCallState()
2025-07-05 16:09:14 +08:00
incomingCall.value = {
callId: data.call_id,
2025-07-09 08:18:59 +08:00
callerId: data.caller_id,
callType: data.call_type === 6 ? "video" : "audio",
content: data.data,
2025-07-05 16:09:14 +08:00
}
callStatus.value = "ringing"
callError.value = null
callConnectionStatus.value = "收到来电"
2025-07-05 16:09:14 +08:00
console.log("收到来电:", incomingCall.value)
}
// 处理通话接受
const handleCallAccepted = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
2025-07-09 08:18:59 +08:00
callStatus.value = "connecting"
callError.value = null
2025-07-09 08:18:59 +08:00
callConnectionStatus.value = "对方已接听,正在连接..."
console.log("通话已被接受,开始交换信令")
// 开始创建Offer
setTimeout(async () => {
try {
await webrtc.getLocalMedia({
audio: true,
video: currentCall.value.callType === "video",
})
await webrtc.createOffer(currentCall.value.callId, currentCall.value.peerId)
} catch (error) {
console.error("创建Offer失败:", error)
endCall("failed")
}
}, 100)
2025-07-05 16:09:14 +08:00
}
}
// 处理通话拒绝
const handleCallRejected = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
2025-07-09 08:18:59 +08:00
resetCallState()
callConnectionStatus.value = "对方已拒绝"
2025-07-05 16:09:14 +08:00
console.log("通话已被拒绝")
// 添加通话记录
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
friend.lastMessage = "[对方已拒绝]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方已拒绝]", friend.unreadCount)
}
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
2025-07-05 16:09:14 +08:00
}
}
// 处理通话结束
const handleCallEnded = (data) => {
2025-07-09 08:18:59 +08:00
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
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
2025-07-09 08:18:59 +08:00
const duration =
callStatus.value === "ongoing"
? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000)
: 0
const callMessage = {
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
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)
}
2025-07-09 08:18:59 +08:00
resetCallState()
callConnectionStatus.value = "通话已结束"
2025-07-05 16:09:14 +08:00
console.log("通话已结束")
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
2025-07-05 16:09:14 +08:00
}
}
// 处理ICE候选
const handleIceCandidate = (data) => {
2025-07-09 08:18:59 +08:00
if (
(currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)
) {
try {
const candidate = JSON.parse(data.data)
console.log("收到ICE候选:", candidate)
2025-07-09 08:18:59 +08:00
// 添加到WebRTC连接
webrtc.addIceCandidate(candidate)
} catch (error) {
console.error("处理ICE候选失败:", error)
}
2025-07-05 16:09:14 +08:00
}
}
// 处理无人接听
const handleCallNoAnswer = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
// 添加未接来电记录
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
}
2025-07-09 08:18:59 +08:00
resetCallState()
callConnectionStatus.value = "对方无人接听"
console.log("对方无人接听")
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方忙线
const handleCallBusy = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
// 添加忙线记录
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
friend.lastMessage = "[对方忙线]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[对方忙线]", friend.unreadCount)
}
2025-07-09 08:18:59 +08:00
resetCallState()
callConnectionStatus.value = "对方忙线中"
console.log("对方忙线中")
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方挂断
const handleCallHangup = (data) => {
2025-07-09 08:18:59 +08:00
const callId = data?.call_id || currentCall.value?.callId || incomingCall.value?.callId
const peerId = data?.caller_id || data?.callee_id || currentCall.value?.peerId || incomingCall.value?.callerId
2025-07-09 08:18:59 +08:00
if (callId && peerId) {
// 添加通话记录
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
2025-07-09 08:18:59 +08:00
const duration =
callStatus.value === "ongoing"
? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000)
: 0
const callMessage = {
2025-07-09 08:18:59 +08:00
type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call",
content: `[对方已挂断] ${Math.floor(duration / 60)}${duration % 60}`,
time: new Date().toLocaleTimeString().slice(0, 5),
senderId: peerId,
read: true,
timestamp: Date.now(),
2025-07-09 08:18:59 +08:00
duration: duration,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
2025-07-09 08:18:59 +08:00
friend.lastMessage = `[对方已挂断] ${Math.floor(duration / 60)}${duration % 60}`
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
}
resetCallState()
callConnectionStatus.value = "对方已挂断"
console.log("对方已挂断通话")
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方掉线
const handleCallDisconnected = (data) => {
const callId = data?.call_id || currentCall.value?.callId || incomingCall.value?.callId
2025-07-09 08:18:59 +08:00
const peerId = data?.caller_id || data?.callee_id || currentCall.value?.peerId || incomingCall.value?.callerId
if (callId && peerId) {
// 添加通话记录
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
2025-07-09 08:18:59 +08:00
const duration =
callStatus.value === "ongoing"
? Math.floor((Date.now() - (currentCall.value?.startTime || incomingCall.value?.startTime)) / 1000)
: 0
const callMessage = {
2025-07-09 08:18:59 +08:00
type: currentCall.value?.callType || incomingCall.value?.callType === "video" ? "video-call" : "audio-call",
content: `[网络中断] ${Math.floor(duration / 60)}${duration % 60}`,
time: new Date().toLocaleTimeString().slice(0, 5),
senderId: peerId,
read: true,
timestamp: Date.now(),
2025-07-09 08:18:59 +08:00
duration: duration,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 更新最后消息
2025-07-09 08:18:59 +08:00
friend.lastMessage = `[网络中断] ${Math.floor(duration / 60)}${duration % 60}`
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, friend.lastMessage, friend.unreadCount)
}
resetCallState()
callConnectionStatus.value = "网络连接已断开"
console.log("网络连接已断开")
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方终止呼叫
const handleCallTerminated = (data) => {
const callId = data.call_id
2025-07-09 08:18:59 +08:00
const peerId = data.caller_id || data.callee_id
2025-07-09 08:18:59 +08:00
if (
(currentCall.value && currentCall.value.callId === callId) ||
(incomingCall.value && incomingCall.value.callId === callId)
) {
// 添加通话记录
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
const callMessage = {
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
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("对方已终止呼叫")
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}
2025-07-05 16:09:14 +08:00
// 发起通话
2025-07-09 08:18:59 +08:00
const startCall = async (peerId, type) => {
if (isInCall.value) {
2025-07-09 08:18:59 +08:00
alert("您正在通话中,请先结束当前通话。")
return
}
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (!friend) {
2025-07-09 08:18:59 +08:00
alert("好友信息无效")
return
}
2025-07-05 16:09:14 +08:00
resetCallState()
2025-07-05 16:09:14 +08:00
const callId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
currentCall.value = {
callId: callId,
peerId: peerId,
callType: type,
startTime: Date.now(),
2025-07-09 08:18:59 +08:00
status: "calling",
2025-07-05 16:09:14 +08:00
}
callStatus.value = "calling"
callConnectionStatus.value = "等待对方接听..."
2025-07-09 08:18:59 +08:00
// 获取本地媒体
try {
await webrtc.getLocalMedia({
audio: true,
video: type === "video",
})
2025-07-09 08:18:59 +08:00
} catch (error) {
console.error("获取本地媒体失败:", error)
resetCallState()
2025-07-09 08:18:59 +08:00
alert("无法访问摄像头或麦克风")
return
}
// 设置30秒超时无人接听
setCallTimeout(() => {
if (callStatus.value === "calling") {
resetCallState()
callConnectionStatus.value = "对方无人接听"
console.log("呼叫超时,对方无人接听")
// 发送无人接听信号
2025-07-09 08:18:59 +08:00
sendCallSignal({
call_status: "no-answer",
call_id: callId,
receiver_user_id: peerId,
})
// 更新好友最后消息为未接来电
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(),
2025-07-09 08:18:59 +08:00
duration: 0,
}
chatDB.saveMessage(callMessage, userStore.currentUser.id, friend.id)
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
}
}, 30000)
2025-07-05 16:09:14 +08:00
// 发送通话请求
2025-07-09 08:18:59 +08:00
sendCallSignal({
call_status: "invite",
call_id: callId,
call_type: type === "video" ? 6 : 7,
receiver_user_id: peerId,
2025-07-05 16:09:14 +08:00
})
console.log("发起通话:", currentCall.value)
}
// 接听来电
2025-07-09 08:18:59 +08:00
const acceptCall = async () => {
2025-07-05 16:09:14 +08:00
if (!incomingCall.value) return
callStatus.value = "connecting"
callConnectionStatus.value = "正在连接..."
2025-07-09 08:18:59 +08:00
try {
// 获取本地媒体
await webrtc.getLocalMedia({
audio: true,
video: incomingCall.value.callType === "video",
})
2025-07-09 08:18:59 +08:00
// 发送接受信号
sendCallSignal({
call_status: "accepted",
call_id: incomingCall.value.callId,
receiver_user_id: incomingCall.value.callerId,
})
currentCall.value = {
callId: incomingCall.value.callId,
peerId: incomingCall.value.callerId,
callType: incomingCall.value.callType,
startTime: Date.now(),
status: "connecting",
}
2025-07-05 16:09:14 +08:00
2025-07-09 08:18:59 +08:00
console.log("接听通话等待Offer...")
} catch (error) {
console.error("接听通话失败:", error)
rejectCall("failed")
}
2025-07-05 16:09:14 +08:00
}
// 拒绝来电
const rejectCall = (reason = "rejected") => {
2025-07-05 16:09:14 +08:00
if (!incomingCall.value) return
// 发送拒绝信号
2025-07-09 08:18:59 +08:00
sendCallSignal({
call_status: reason,
call_id: incomingCall.value.callId,
receiver_user_id: incomingCall.value.callerId,
2025-07-05 16:09:14 +08:00
})
// 添加通话记录
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === incomingCall.value.callerId)
if (friend) {
const content = reason === "busy" ? "[对方忙线]" : "[已拒绝]"
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(),
2025-07-09 08:18:59 +08:00
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" ? "对方忙线" : "已拒绝"
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
2025-07-05 16:09:14 +08:00
console.log("拒绝通话")
}
// 结束通话
const endCall = (reason = "ended") => {
2025-07-05 16:09:14 +08:00
const callId = currentCall.value?.callId || incomingCall.value?.callId
const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
const callTypeVal = currentCall.value?.callType || incomingCall.value?.callType
2025-07-05 16:09:14 +08:00
2025-07-09 08:18:59 +08:00
if (!callId || !peerId) {
resetCallState()
return
}
2025-07-05 16:09:14 +08:00
// 发送结束信号
2025-07-09 08:18:59 +08:00
sendCallSignal({
call_status: reason,
call_id: callId,
receiver_user_id: peerId,
2025-07-05 16:09:14 +08:00
})
// 添加通话记录
2025-07-09 08:18:59 +08:00
const friend = friends.value.find((f) => f.id === peerId)
if (friend) {
2025-07-09 08:18:59 +08:00
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) {
2025-07-09 08:18:59 +08:00
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(),
2025-07-09 08:18:59 +08:00
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" ? "对方无人接听" : "通话结束"
// 5秒后清除状态
setTimeout(() => {
callConnectionStatus.value = ""
}, 5000)
2025-07-05 16:09:14 +08:00
console.log("结束通话")
}
2025-07-02 16:54:52 +08:00
return {
friends,
currentFriend,
messages,
connectionStatus,
socket,
2025-07-05 16:09:14 +08:00
currentCall,
incomingCall,
callStatus,
callError,
callConnectionStatus,
2025-07-02 16:54:52 +08:00
isConnected,
2025-07-05 16:09:14 +08:00
isInCall,
isCalling,
isRinging,
isConnecting,
2025-07-05 16:09:14 +08:00
isOngoingCall,
isDisconnected,
isHangup,
initDB,
2025-07-02 16:54:52 +08:00
loadFriends,
setCurrentFriend,
switchFriend,
2025-07-02 16:54:52 +08:00
addMessage,
handleIncomingMessage,
2025-07-05 16:09:14 +08:00
handleCallSignal,
startCall,
acceptCall,
rejectCall,
endCall,
resetCallState,
sendCallSignal,
2025-07-09 08:18:59 +08:00
clearCallTimeout,
2025-07-02 16:54:52 +08:00
}
2025-07-09 08:18:59 +08:00
})