目前对方可以接收到通话申请了(接电话有通话中判断错误导致重复send-to-user路由的问题)

This commit is contained in:
2025-07-07 16:51:55 +08:00
parent 851fc5e915
commit 5fe9c29d6e
7 changed files with 366 additions and 102 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
<title>奶酪-即时通讯-demo</title>
</head>
<body>
<div id="app"></div>

View File

@@ -19,6 +19,9 @@
<span class="status-text">
{{ isFriendOnline ? '在线' : '离线' }}
</span>
<span v-if="callStatus" class="call-status-text">
{{ callStatusText }}
</span>
</p>
</div>
</div>
@@ -64,62 +67,49 @@
</div>
</div>
</div>
<!-- 视频通话组件 -->
<VideoCallComponent
v-if="showVideoCall"
:caller-info="callerInfo"
:call-type="callType"
:is-incoming="isIncoming"
@accept-call="handleAcceptCall"
@reject-call="handleRejectCall"
@end-call="handleEndCall"
/>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { ref, computed } from 'vue';
import { useChatStore } from '@/stores/chat';
import { useThemeStore } from '@/stores/theme';
import VideoCallComponent from './VideoCallComponent.vue';
const chatStore = useChatStore();
const themeStore = useThemeStore();
const isFriendOnline = ref(true);
const showMoreActions = ref(false);
const showVideoCall = ref(false);
const callType = ref('video');
const isIncoming = ref(false);
const callerInfo = ref(null);
// 监听通话状态变化
watch(() => chatStore.callStatus, (status) => {
if (status === 'calling' || status === 'ringing' || status === 'ongoing') {
showVideoCall.value = true;
} else {
showVideoCall.value = false;
// 获取当前通话状态
const callStatus = computed(() => {
if (!chatStore.currentFriend) return null;
// 检查是否正在与当前好友通话
if (chatStore.currentCall && chatStore.currentCall.peerId === chatStore.currentFriend.id) {
return chatStore.callConnectionStatus || '通话中';
}
return null;
});
// 监听来电
watch(() => chatStore.incomingCall, (call) => {
if (call) {
isIncoming.value = true;
callerInfo.value = chatStore.friends.find(f => f.id === call.callerId);
callType.value = call.callType;
} else {
isIncoming.value = false;
}
});
// 获取通话状态文本
const callStatusText = computed(() => {
if (!callStatus.value) return '';
// 监听当前通话
watch(() => chatStore.currentCall, (call) => {
if (call) {
callerInfo.value = chatStore.friends.find(f => f.id === call.peerId);
callType.value = call.callType;
isIncoming.value = false;
switch (callStatus.value) {
case '等待对方接听...':
return '📞 呼叫中...';
case '通话中':
return '📞 通话中';
case '对方已拒绝':
return '❌ 已拒绝';
case '对方无人接听':
return '🕒 未接听';
case '对方忙线中':
return '🚫 忙线中';
default:
return '📞 ' + callStatus.value;
}
});
@@ -152,21 +142,6 @@ const startVideoCall = () => {
chatStore.startCall(chatStore.currentFriend.id, 'video');
};
// 接听来电
const handleAcceptCall = () => {
chatStore.acceptCall();
};
// 拒绝来电
const handleRejectCall = () => {
chatStore.rejectCall();
};
// 结束通话
const handleEndCall = () => {
chatStore.endCall();
};
// 清空聊天记录
const clearChatHistory = () => {
if (confirm('确定要清空与该好友的聊天记录吗?')) {
@@ -184,7 +159,6 @@ const viewFriendProfile = () => {
</script>
<style scoped>
/* 保持原有样式不变 */
.chat-header {
background: #ffffff;
border-bottom: 1px solid #e2e8f0;
@@ -278,6 +252,22 @@ const viewFriendProfile = () => {
color: #ef4444;
}
/* 添加通话状态样式 */
.call-status-text {
margin-left: 8px;
font-size: 13px;
color: #3b82f6;
font-weight: 500;
background: rgba(59, 130, 246, 0.1);
padding: 2px 6px;
border-radius: 4px;
}
.chat-header.dark .call-status-text {
color: #93c5fd;
background: rgba(59, 130, 246, 0.2);
}
.header-actions {
display: flex;
align-items: center;
@@ -427,5 +417,9 @@ const viewFriendProfile = () => {
.header-actions {
gap: 8px;
}
.call-status-text {
font-size: 11px;
}
}
</style>

View File

@@ -22,7 +22,7 @@
</div>
<div class="caller-details">
<div class="caller-name">{{ callerInfo.name }}</div>
<div class="call-status">{{ callStatus }} {{ formattedDuration }}</div>
<div class="call-status">{{ connectionText }}</div>
</div>
</div>
@@ -120,12 +120,18 @@
<div v-if="!isMinimized" class="call-controls">
<!-- 来电控制按钮 -->
<div v-if="isIncoming && !isOngoing" class="incoming-call-controls">
<button class="accept-btn" @click="acceptCall">
<i class="fas fa-phone"></i> 接听
</button>
<button class="reject-btn" @click="rejectCall">
<i class="fas fa-phone-slash"></i> 拒绝
</button>
<div v-if="callStatus === 'busy'" class="busy-status">
<i class="fas fa-phone-slash"></i>
<span>对方忙线中</span>
</div>
<template v-else>
<button class="accept-btn" @click="acceptCall">
<i class="fas fa-phone"></i> 接听
</button>
<button class="reject-btn" @click="rejectCall">
<i class="fas fa-phone-slash"></i> 拒绝
</button>
</template>
</div>
<template v-else>
@@ -205,7 +211,6 @@ const isCameraOn = ref(props.callType === 'video');
const isSpeakerOn = ref(true);
const hasLocalVideo = ref(false);
const hasRemoteVideo = ref(false);
const connectionText = ref('正在连接...');
const callDuration = ref(0);
const callTimer = ref(null);
const isCameraWorking = ref(true);
@@ -223,6 +228,35 @@ const localVideo = ref(null);
const remoteVideo = ref(null);
const remoteVideoPreview = ref(null);
// 获取当前通话状态
const callStatus = computed(() => {
return chatStore.callError;
});
// 连接状态文本
const connectionText = computed(() => {
// 优先使用 chatStore 中的详细状态
if (chatStore.callConnectionStatus) {
return chatStore.callConnectionStatus;
}
// 处理忙线状态
if (chatStore.callError === 'busy') {
return '对方忙线中';
}
// 默认状态显示
if (props.isIncoming && !isOngoing.value) {
return '来电中...';
}
if (isOngoing.value) {
return `通话中 ${formattedDuration.value}`;
}
return '正在连接...';
});
// 通话计时器
const startCallTimer = () => {
if (callTimer.value) clearInterval(callTimer.value);
@@ -248,14 +282,6 @@ const containerStyle = computed(() => {
};
});
// 通话状态
const callStatus = computed(() => {
if (props.isIncoming && !isOngoing.value) {
return '来电中...';
}
return '通话中';
});
// 初始化媒体设备
const initializeMedia = async () => {
try {
@@ -267,13 +293,11 @@ const initializeMedia = async () => {
// 获取本地媒体流
await webrtc.getLocalMedia(constraints);
connectionText.value = '已连接';
startCallTimer();
} catch (error) {
console.error('无法访问媒体设备:', error);
isCameraOn.value = false;
isCameraWorking.value = false;
connectionText.value = '连接失败';
}
};
@@ -331,7 +355,6 @@ const endCall = () => {
isVisible.value = false;
emit('end-call');
chatStore.endCall();
};
// 接听通话
@@ -345,7 +368,6 @@ const acceptCall = () => {
const rejectCall = () => {
isVisible.value = false;
emit('reject-call');
chatStore.rejectCall();
};
// 拖拽功能
@@ -478,7 +500,6 @@ onUnmounted(() => {
</script>
<style scoped>
/* 保持原有样式不变 */
.video-call-container {
position: fixed;
z-index: 9999;
@@ -849,6 +870,22 @@ onUnmounted(() => {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
/* 添加忙线状态样式 */
.busy-status {
background: rgba(0, 0, 0, 0.7);
color: #ff6b6b;
padding: 12px 24px;
border-radius: 30px;
font-size: 16px;
display: flex;
align-items: center;
gap: 10px;
}
.busy-status i {
font-size: 20px;
}
/* 响应式设计 */
@media (max-width: 600px) {
.video-call-container:not(.minimized) {

View File

@@ -54,13 +54,13 @@ export function useWebSocket() {
const data = JSON.parse(event.data)
console.log("收到WebSocket消息:", data)
// 处理通话信令
// 优先处理通话信令(无论是否在当前聊天窗口)
if (data.call_id && data.call_status) {
handleCallSignal(data)
return
}
// 处理普通消息
// 处理普通消息 - 添加来源判断防止循环
if (data.sender_user_id && data.receiver_user_id) {
chatStore.handleIncomingMessage(data, userStore.currentUser?.id)
}

View File

@@ -16,6 +16,8 @@ export const useChatStore = defineStore("chat", () => {
const currentCall = ref(null)
const incomingCall = ref(null)
const callStatus = ref("idle") // idle, calling, ringing, ongoing, ended
const callError = ref(null) // 错误状态:'rejected', 'no-answer', 'busy'
const callConnectionStatus = ref("") // 连接状态:等待接听、正在连接等
const webrtc = useWebRTC()
const userStore = useUserStore()
@@ -69,7 +71,7 @@ export const useChatStore = defineStore("chat", () => {
friend.lastMessage = ""
friend.unreadCount = 0
})
friends.value = friend
friends.value = friendList
}
}
@@ -130,6 +132,12 @@ export const useChatStore = defineStore("chat", () => {
// 处理接收到的消息
const handleIncomingMessage = async (messageData, currentUserId) => {
// 添加来源检查 - 防止循环
if (messageData.isFromHttp) {
console.log("忽略来自HTTP的消息避免循环")
return
}
const senderId = messageData.sender_user_id
const receiverId = messageData.receiver_user_id
@@ -162,7 +170,6 @@ export const useChatStore = defineStore("chat", () => {
} else {
// 更新未读消息计数
const friend = friends.value.find((f) => f.id == senderId)
console.log(f.id == senderId, senderId, f)
if (friend) {
friend.unreadCount = (friend.unreadCount || 0) + 1
@@ -213,11 +220,40 @@ export const useChatStore = defineStore("chat", () => {
case "candidate":
handleIceCandidate(data)
break
case "no-answer": // 添加无人接听状态
handleCallNoAnswer(data)
break
case "connecting": // 新增连接状态
callConnectionStatus.value = "正在连接..."
break
case "busy": // 对方忙线中
handleCallBusy(data)
break
}
}
// 处理来电
const handleIncomingCall = (data) => {
// 如果自己正在通话中,自动拒绝并发送忙线状态
if (isInCall.value) {
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: data.sender_user_id,
type: data.message_type === 6 ? "video-call" : "audio-call",
callId: data.call_id,
callStatus: "busy"
})
// 添加未接来电消息
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)
}
return
}
incomingCall.value = {
callId: data.call_id,
callerId: data.sender_user_id,
@@ -225,6 +261,8 @@ export const useChatStore = defineStore("chat", () => {
content: data.content
}
callStatus.value = "ringing"
callError.value = null
callConnectionStatus.value = "收到来电"
console.log("收到来电:", incomingCall.value)
}
@@ -233,6 +271,8 @@ export const useChatStore = defineStore("chat", () => {
const handleCallAccepted = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
callStatus.value = "ongoing"
callError.value = null
callConnectionStatus.value = "通话中"
console.log("通话已被接受")
}
}
@@ -241,8 +281,17 @@ export const useChatStore = defineStore("chat", () => {
const handleCallRejected = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
callStatus.value = "ended"
callError.value = "rejected"
callConnectionStatus.value = "对方已拒绝"
currentCall.value = null
console.log("通话已被拒绝")
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
callError.value = null
callConnectionStatus.value = ""
}, 5000)
}
}
@@ -251,9 +300,17 @@ export const useChatStore = defineStore("chat", () => {
if ((currentCall.value && currentCall.value.callId === data.call_id) ||
(incomingCall.value && incomingCall.value.callId === data.call_id)) {
callStatus.value = "ended"
callError.value = null
callConnectionStatus.value = "通话已结束"
currentCall.value = null
incomingCall.value = null
console.log("通话已结束")
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
callConnectionStatus.value = ""
}, 5000)
}
}
@@ -265,21 +322,102 @@ export const useChatStore = defineStore("chat", () => {
}
}
// 处理无人接听
const handleCallNoAnswer = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
callStatus.value = "ended"
callError.value = "no-answer"
callConnectionStatus.value = "对方无人接听"
currentCall.value = null
console.log("对方无人接听")
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
callError.value = null
callConnectionStatus.value = ""
}, 5000)
}
}
// 处理对方忙线
const handleCallBusy = (data) => {
if (currentCall.value && currentCall.value.callId === data.call_id) {
callStatus.value = "ended"
callError.value = "busy"
callConnectionStatus.value = "对方忙线中"
currentCall.value = null
console.log("对方忙线中")
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
callError.value = null
callConnectionStatus.value = ""
}, 5000)
}
}
// 发起通话
const startCall = (peerId, type) => {
if (isInCall.value) return
if (isInCall.value) {
alert('您正在通话中,请先结束当前通话。')
return
}
const friend = friends.value.find(f => f.id === peerId)
if (!friend) {
alert('好友信息无效')
return
}
const callId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
currentCall.value = {
callId: callId,
peerId: peerId,
callType: type
callType: type,
startTime: Date.now(),
status: 'calling'
}
callStatus.value = "calling"
callError.value = null
callConnectionStatus.value = "等待对方接听..."
// 修复参数传递问题
// 设置30秒超时无人接听
const timeoutId = setTimeout(() => {
if (callStatus.value === "calling") {
callStatus.value = "ended"
callError.value = "no-answer"
callConnectionStatus.value = "对方无人接听"
console.log("呼叫超时,对方无人接听")
// 发送无人接听信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
type: type === "video" ? "video-call" : "audio-call",
callId: callId,
callStatus: "no-answer"
})
// 更新好友最后消息为未接来电
friend.lastMessage = "[未接来电]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[未接来电]", friend.unreadCount)
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
currentCall.value = null
callConnectionStatus.value = ""
}, 5000)
}
}, 30000)
currentCall.value.timeoutId = timeoutId
// 发送通话请求
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
@@ -295,7 +433,12 @@ export const useChatStore = defineStore("chat", () => {
const acceptCall = () => {
if (!incomingCall.value) return
// 修复参数传递问题
// 清除可能存在的超时计时器
if (currentCall.value?.timeoutId) {
clearTimeout(currentCall.value.timeoutId)
}
// 发送接受信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: incomingCall.value.callerId,
@@ -307,11 +450,15 @@ export const useChatStore = defineStore("chat", () => {
currentCall.value = {
callId: incomingCall.value.callId,
peerId: incomingCall.value.callerId,
callType: incomingCall.value.callType
callType: incomingCall.value.callType,
startTime: Date.now(),
status: 'ongoing'
}
callStatus.value = "ongoing"
callConnectionStatus.value = "通话中"
incomingCall.value = null
callError.value = null
console.log("接听通话")
}
@@ -319,7 +466,7 @@ export const useChatStore = defineStore("chat", () => {
const rejectCall = () => {
if (!incomingCall.value) return
// 修复参数传递问题
// 发送拒绝信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: incomingCall.value.callerId,
@@ -328,8 +475,26 @@ export const useChatStore = defineStore("chat", () => {
callStatus: "rejected"
})
callStatus.value = "idle"
callStatus.value = "ended"
callConnectionStatus.value = "已拒绝"
callError.value = "rejected"
// 更新好友最后消息为已拒绝
const friend = friends.value.find(f => f.id === incomingCall.value.callerId)
if (friend) {
friend.lastMessage = "[已拒绝]"
chatDB.updateFriendLastMessage(friend.id, userStore.currentUser.id, "[已拒绝]", friend.unreadCount)
}
incomingCall.value = null
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
callError.value = null
callConnectionStatus.value = ""
}, 5000)
console.log("拒绝通话")
}
@@ -337,22 +502,35 @@ export const useChatStore = defineStore("chat", () => {
const endCall = () => {
const callId = currentCall.value?.callId || incomingCall.value?.callId
const peerId = currentCall.value?.peerId || incomingCall.value?.callerId
const callType = currentCall.value?.callType || incomingCall.value?.callType
const callTypeVal = currentCall.value?.callType || incomingCall.value?.callType
if (!callId || !peerId) return
// 修复参数传递问题
// 清除超时计时器
if (currentCall.value?.timeoutId) {
clearTimeout(currentCall.value.timeoutId)
}
// 发送结束信号
sendMessage({
senderId: userStore.currentUser?.id,
receiverId: peerId,
type: callType === "video" ? "video-call" : "audio-call",
type: callTypeVal === "video" ? "video-call" : "audio-call",
callId: callId,
callStatus: "ended"
})
callStatus.value = "idle"
currentCall.value = null
incomingCall.value = null
callStatus.value = "ended"
callConnectionStatus.value = "通话结束"
// 5秒后清除状态
setTimeout(() => {
callStatus.value = "idle"
currentCall.value = null
incomingCall.value = null
callError.value = null
callConnectionStatus.value = ""
}, 5000)
console.log("结束通话")
}
@@ -365,6 +543,8 @@ export const useChatStore = defineStore("chat", () => {
currentCall,
incomingCall,
callStatus,
callError,
callConnectionStatus,
isConnected,
isInCall,
isCalling,

View File

@@ -34,7 +34,7 @@ service.interceptors.response.use(
localStorage.removeItem("token")
localStorage.removeItem("chatUser")
window.location.href = "/login"
return Promise.reject(new(msg))
return Promise.reject(new Error(msg))
}
message.error(msg)
return Promise.reject(new Error(msg))
@@ -129,6 +129,8 @@ export const sendMessage = (data) => {
receiver_user_id: data.receiverId,
message_type: messageTypeMap[data.type] || 0,
message_content: data.content || "",
// 添加来源标记
isFromHttp: true
}
// 如果包含通话信令添加call_id和call_status

View File

@@ -13,6 +13,17 @@
<!-- 录音状态指示器 -->
<RecordingIndicator />
<!-- 视频通话组件移动到此处 -->
<VideoCallComponent
v-if="showVideoCall"
:caller-info="callerInfo"
:call-type="callType"
:is-incoming="isIncoming"
@accept-call="handleAcceptCall"
@reject-call="handleRejectCall"
@end-call="handleEndCall"
/>
<!-- 主聊天容器 -->
<div class="flex-1 max-w-7xl mx-auto bg-white/90 backdrop-blur-sm rounded-2xl shadow-2xl overflow-hidden flex z-10">
<!-- 侧边导航 -->
@@ -39,7 +50,7 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, provide } from 'vue';
import {onMounted, onUnmounted, ref, watch} from 'vue';
import { useUserStore } from '@/stores/user';
import { useChatStore } from '@/stores/chat';
import { useThemeStore } from '@/stores/theme';
@@ -55,6 +66,7 @@ import MediaPreview from '@/components/MediaPreview.vue';
import FriendsManagement from '@/components/FriendsManagement.vue';
import GroupsManagement from '@/components/GroupsManagement.vue';
import MomentsView from '@/components/MomentsView.vue';
import VideoCallComponent from '@/components/VideoCallComponent.vue';
const userStore = useUserStore();
const chatStore = useChatStore();
@@ -62,23 +74,62 @@ const themeStore = useThemeStore();
const currentNav = ref('chat');
// 视频通话相关状态
const showVideoCall = ref(false);
const callType = ref('video');
const isIncoming = ref(false);
const callerInfo = ref(null);
// 初始化WebSocket连接
const { connectWebSocket, disconnectWebSocket } = useWebSocket();
// 初始化录音功能
const { isRecording } = useRecording();
// 提供录音状态给子组件
provide('isRecording', isRecording);
// 处理导航切换
const handleNavChange = (nav) => {
currentNav.value = nav;
};
// 检查用户会话
userStore.checkSession();
// 监听通话状态变化
watch(() => chatStore.callStatus, (status) => {
showVideoCall.value = status !== 'idle';
});
// 监听来电
watch(() => chatStore.incomingCall, (call) => {
if (call) {
isIncoming.value = true;
callerInfo.value = chatStore.friends.find(f => f.id === call.callerId);
callType.value = call.callType;
} else {
isIncoming.value = false;
}
});
// 监听当前通话
watch(() => chatStore.currentCall, (call) => {
if (call) {
callerInfo.value = chatStore.friends.find(f => f.id === call.peerId);
callType.value = call.callType;
isIncoming.value = false;
}
});
// 处理接听通话
const handleAcceptCall = () => {
chatStore.acceptCall();
};
// 处理拒绝通话
const handleRejectCall = () => {
chatStore.rejectCall();
};
// 处理结束通话
const handleEndCall = () => {
chatStore.endCall();
};
// 加载主题
themeStore.loadTheme();
@@ -130,4 +181,4 @@ onUnmounted(() => {
.animate-float-slow {
animation: float-slow 18s infinite ease-in-out;
}
</style>
</style>