|
@@ -1,204 +1,214 @@
|
|
<template>
|
|
<template>
|
|
- <div v-if="rtcStore.imSate.videoCallModal" class="weixin-call-modal">
|
|
|
|
- <div class="caller-info">
|
|
|
|
- <img
|
|
|
|
- class="avatar"
|
|
|
|
- :src="rtcStore.imSate.callAvatar || defaultAvatar"
|
|
|
|
- alt="头像"
|
|
|
|
- />
|
|
|
|
- <div class="name">{{ rtcStore.imSate.callName || "未知用户" }}</div>
|
|
|
|
- <div class="status">{{ statusText }}</div>
|
|
|
|
- </div>
|
|
|
|
- <div class="btn-group">
|
|
|
|
- <!-- 拨打方,只显示挂断按钮 -->
|
|
|
|
- <template v-if="rtcStore.isCaller">
|
|
|
|
- <button class="btn hangup" @click="hangupCall">挂断</button>
|
|
|
|
- </template>
|
|
|
|
-
|
|
|
|
- <!-- 来电方,显示接听和拒绝按钮(未通话中) -->
|
|
|
|
- <template v-else-if="!inCall">
|
|
|
|
- <button class="btn reject" @click="rejectCall">拒绝</button>
|
|
|
|
- <button class="btn accept" @click="acceptCall">接听</button>
|
|
|
|
- </template>
|
|
|
|
-
|
|
|
|
- <!-- 通话中显示挂断按钮 -->
|
|
|
|
- <template v-else>
|
|
|
|
- <button class="btn hangup" @click="hangupCall">挂断</button>
|
|
|
|
- </template>
|
|
|
|
- </div>
|
|
|
|
|
|
+ <div v-if="rtcStore.imSate.videoCallModal" class="weixin-call-modal">
|
|
|
|
+ <div class="caller-info">
|
|
|
|
+ <img
|
|
|
|
+ class="avatar"
|
|
|
|
+ :src="rtcStore.imSate.callAvatar || defaultAvatar"
|
|
|
|
+ alt="头像"
|
|
|
|
+ />
|
|
|
|
+ <div class="name">{{ rtcStore.imSate.callName || "未知用户" }}</div>
|
|
|
|
+ <div class="status">{{ statusText }}</div>
|
|
</div>
|
|
</div>
|
|
- </template>
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- <script setup>
|
|
|
|
- import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|
|
|
- import { useWebSocketStore } from "@/stores/modules/webSocketStore";
|
|
|
|
- import { useWebRTCStore } from "@/stores/modules/webrtcStore";
|
|
|
|
- import * as Constant from "@/common/constant/Constant";
|
|
|
|
-
|
|
|
|
- const wsStore = useWebSocketStore();
|
|
|
|
- const rtcStore = useWebRTCStore();
|
|
|
|
-
|
|
|
|
- const inCall = ref(false);
|
|
|
|
- const defaultAvatar = "https://example.com/default-avatar.png";
|
|
|
|
-
|
|
|
|
- const statusText = computed(() => {
|
|
|
|
- return inCall.value ? "通话中..." : "正在语音通话请求...";
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- function onMessage(message) {
|
|
|
|
- if (message.type !== Constant.MESSAGE_TRANS_TYPE) return;
|
|
|
|
-
|
|
|
|
- switch (message.contentType) {
|
|
|
|
- case Constant.DIAL_AUDIO_ONLINE:
|
|
|
|
- rtcStore.imSate.videoCallModal = true;
|
|
|
|
- rtcStore.imSate.callName = message.fromUserName || "未知用户";
|
|
|
|
- rtcStore.imSate.callAvatar = message.fromUserAvatar || "";
|
|
|
|
- inCall.value = false; // 有来电,未接听
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- case Constant.ACCEPT_AUDIO_ONLINE:
|
|
|
|
- inCall.value = true; // 对方已接听,通话中
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- case Constant.REJECT_AUDIO_ONLINE:
|
|
|
|
- case Constant.CANCELL_AUDIO_ONLINE:
|
|
|
|
- case Constant.DIAL_MEDIA_END:
|
|
|
|
- rtcStore.imSate.videoCallModal = false;
|
|
|
|
- inCall.value = false;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- async function acceptCall() {
|
|
|
|
- try {
|
|
|
|
- inCall.value = true;
|
|
|
|
- wsStore.sendMessage({
|
|
|
|
- messageType: Constant.MESSAGE_TYPE_USER,
|
|
|
|
- contentType: Constant.ACCEPT_AUDIO_ONLINE,
|
|
|
|
- type: Constant.MESSAGE_TRANS_TYPE,
|
|
|
|
- });
|
|
|
|
- // 可以调用 rtcStore 初始化本地流、连接等逻辑
|
|
|
|
- } catch (err) {
|
|
|
|
- console.error("接听失败", err);
|
|
|
|
|
|
+ <div class="btn-group">
|
|
|
|
+ <!-- 拨打方,只显示挂断按钮 -->
|
|
|
|
+ <template v-if="rtcStore.isCaller">
|
|
|
|
+ <button class="btn hangup" @click="hangupCall">挂断</button>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <!-- 来电方,显示接听和拒绝按钮(未通话中) -->
|
|
|
|
+ <template v-else-if="!inCall">
|
|
|
|
+ <button class="btn reject" @click="rejectCall">拒绝</button>
|
|
|
|
+ <button class="btn accept" @click="acceptCall">接听</button>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <!-- 通话中显示挂断按钮 -->
|
|
|
|
+ <template v-else>
|
|
|
|
+ <button class="btn hangup" @click="hangupCall">挂断</button>
|
|
|
|
+ </template>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|
|
|
+import { useWebSocketStore } from "@/stores/modules/webSocketStore";
|
|
|
|
+import { useWebRTCStore } from "@/stores/modules/webrtcStore";
|
|
|
|
+import * as Constant from "@/common/constant/Constant";
|
|
|
|
+
|
|
|
|
+const wsStore = useWebSocketStore();
|
|
|
|
+const rtcStore = useWebRTCStore();
|
|
|
|
+
|
|
|
|
+const inCall = ref(false);
|
|
|
|
+const defaultAvatar = "https://example.com/default-avatar.png";
|
|
|
|
+
|
|
|
|
+const statusText = computed(() => {
|
|
|
|
+ if(inCall.value && rtcStore.remoteStream){
|
|
|
|
+ return "通话中..."
|
|
|
|
+ }
|
|
|
|
+ return inCall.value ? "链接中..." : "正在语音通话请求...";
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+function onMessage(message) {
|
|
|
|
+ if (message.type !== Constant.MESSAGE_TRANS_TYPE) return;
|
|
|
|
+
|
|
|
|
+ switch (message.contentType) {
|
|
|
|
+ case Constant.DIAL_AUDIO_ONLINE:
|
|
|
|
+ rtcStore.imSate.videoCallModal = true;
|
|
|
|
+ rtcStore.imSate.callName = message.fromUserName || "未知用户";
|
|
|
|
+ rtcStore.imSate.callAvatar = message.fromUserAvatar || "";
|
|
|
|
+ inCall.value = false; // 有来电,未接听
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Constant.ACCEPT_AUDIO_ONLINE:
|
|
|
|
+ inCall.value = true; // 对方已接听,通话中
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Constant.REJECT_AUDIO_ONLINE:
|
|
|
|
+ case Constant.CANCELL_AUDIO_ONLINE:
|
|
|
|
+ case Constant.DIAL_MEDIA_END:
|
|
|
|
+ rtcStore.imSate.videoCallModal = false;
|
|
inCall.value = false;
|
|
inCall.value = false;
|
|
- }
|
|
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
-
|
|
|
|
- function rejectCall() {
|
|
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function acceptCall() {
|
|
|
|
+ try {
|
|
|
|
+ inCall.value = true;
|
|
wsStore.sendMessage({
|
|
wsStore.sendMessage({
|
|
messageType: Constant.MESSAGE_TYPE_USER,
|
|
messageType: Constant.MESSAGE_TYPE_USER,
|
|
- contentType: Constant.REJECT_AUDIO_ONLINE,
|
|
|
|
|
|
+ contentType: Constant.ACCEPT_AUDIO_ONLINE,
|
|
type: Constant.MESSAGE_TRANS_TYPE,
|
|
type: Constant.MESSAGE_TRANS_TYPE,
|
|
});
|
|
});
|
|
- rtcStore.imSate.videoCallModal = false;
|
|
|
|
|
|
+ // 可以调用 rtcStore 初始化本地流、连接等逻辑
|
|
|
|
+ } catch (err) {
|
|
|
|
+ console.error("接听失败", err);
|
|
inCall.value = false;
|
|
inCall.value = false;
|
|
}
|
|
}
|
|
-
|
|
|
|
- function hangupCall() {
|
|
|
|
- rtcStore.cleanup();
|
|
|
|
- wsStore.sendMessage({
|
|
|
|
- messageType: Constant.MESSAGE_TYPE_USER,
|
|
|
|
- contentType: Constant.CANCELL_AUDIO_ONLINE,
|
|
|
|
- type: Constant.MESSAGE_TRANS_TYPE,
|
|
|
|
- });
|
|
|
|
- rtcStore.imSate.videoCallModal = false;
|
|
|
|
- inCall.value = false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- onMounted(() => {
|
|
|
|
- wsStore.onMessageCallbacks.push(onMessage);
|
|
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function rejectCall() {
|
|
|
|
+ wsStore.sendMessage({
|
|
|
|
+ messageType: Constant.MESSAGE_TYPE_USER,
|
|
|
|
+ contentType: Constant.REJECT_AUDIO_ONLINE,
|
|
|
|
+ type: Constant.MESSAGE_TRANS_TYPE,
|
|
});
|
|
});
|
|
-
|
|
|
|
- onBeforeUnmount(() => {
|
|
|
|
- wsStore.onMessageCallbacks = wsStore.onMessageCallbacks.filter(
|
|
|
|
- (cb) => cb !== onMessage
|
|
|
|
- );
|
|
|
|
|
|
+ rtcStore.imSate.videoCallModal = false;
|
|
|
|
+ inCall.value = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function hangupCall() {
|
|
|
|
+ rtcStore.cleanup();
|
|
|
|
+ wsStore.sendMessage({
|
|
|
|
+ messageType: Constant.MESSAGE_TYPE_USER,
|
|
|
|
+ contentType: Constant.CANCELL_AUDIO_ONLINE,
|
|
|
|
+ type: Constant.MESSAGE_TRANS_TYPE,
|
|
});
|
|
});
|
|
- </script>
|
|
|
|
-
|
|
|
|
- <style scoped lang="less">
|
|
|
|
- .weixin-call-modal {
|
|
|
|
- position: fixed;
|
|
|
|
- inset: 0;
|
|
|
|
- background-color: rgba(0, 0, 0, 0.85);
|
|
|
|
- display: flex;
|
|
|
|
- flex-direction: column;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
- z-index: 9999;
|
|
|
|
- color: white;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .caller-info {
|
|
|
|
- display: flex;
|
|
|
|
- flex-direction: column;
|
|
|
|
- align-items: center;
|
|
|
|
- margin-bottom: 80px;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .avatar {
|
|
|
|
- width: 80px;
|
|
|
|
- height: 80px;
|
|
|
|
- border-radius: 50%;
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
- border: 2px solid rgba(255, 255, 255, 0.2);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .name {
|
|
|
|
- font-size: 20px;
|
|
|
|
- font-weight: bold;
|
|
|
|
- margin-bottom: 8px;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .status {
|
|
|
|
- font-size: 14px;
|
|
|
|
- color: #ccc;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn-group {
|
|
|
|
- display: flex;
|
|
|
|
- gap: 60px;
|
|
|
|
- }
|
|
|
|
|
|
+ rtcStore.imSate.videoCallModal = false;
|
|
|
|
+ inCall.value = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => rtcStore.remoteStream,
|
|
|
|
+ (val) => {
|
|
|
|
+ if (val && rtcStore.streamType == "audio") {
|
|
|
|
|
|
- .btn {
|
|
|
|
- width: 64px;
|
|
|
|
- height: 64px;
|
|
|
|
- border-radius: 50%;
|
|
|
|
- border: none;
|
|
|
|
- font-size: 14px;
|
|
|
|
- color: white;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
- cursor: pointer;
|
|
|
|
- user-select: none;
|
|
|
|
- outline: none;
|
|
|
|
- transition: background-color 0.3s ease;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn.accept {
|
|
|
|
- background-color: #4cd964;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn.accept:hover {
|
|
|
|
- background-color: #40c150;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn.reject {
|
|
|
|
- background-color: #ff3b30;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn.reject:hover {
|
|
|
|
- background-color: #e02d22;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn.hangup {
|
|
|
|
- background-color: #ff3b30;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- .btn.hangup:hover {
|
|
|
|
- background-color: #e02d22;
|
|
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- </style>
|
|
|
|
-
|
|
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ wsStore.onMessageCallbacks.push(onMessage);
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
+ wsStore.onMessageCallbacks = wsStore.onMessageCallbacks.filter(
|
|
|
|
+ (cb) => cb !== onMessage
|
|
|
|
+ );
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped lang="less">
|
|
|
|
+.weixin-call-modal {
|
|
|
|
+ position: fixed;
|
|
|
|
+ inset: 0;
|
|
|
|
+ background-color: rgba(0, 0, 0, 0.85);
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ z-index: 9999;
|
|
|
|
+ color: white;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.caller-info {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 80px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.avatar {
|
|
|
|
+ width: 80px;
|
|
|
|
+ height: 80px;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
+ border: 2px solid rgba(255, 255, 255, 0.2);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.name {
|
|
|
|
+ font-size: 20px;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.status {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: #ccc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn-group {
|
|
|
|
+ display: flex;
|
|
|
|
+ gap: 60px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn {
|
|
|
|
+ width: 64px;
|
|
|
|
+ height: 64px;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ border: none;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: white;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ user-select: none;
|
|
|
|
+ outline: none;
|
|
|
|
+ transition: background-color 0.3s ease;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn.accept {
|
|
|
|
+ background-color: #4cd964;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn.accept:hover {
|
|
|
|
+ background-color: #40c150;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn.reject {
|
|
|
|
+ background-color: #ff3b30;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn.reject:hover {
|
|
|
|
+ background-color: #e02d22;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn.hangup {
|
|
|
|
+ background-color: #ff3b30;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn.hangup:hover {
|
|
|
|
+ background-color: #e02d22;
|
|
|
|
+}
|
|
|
|
+</style>
|