wkw 4 天之前
父节点
当前提交
def77292f3
共有 4 个文件被更改,包括 154 次插入88 次删除
  1. 2 0
      src/App.vue
  2. 50 0
      src/stores/modules/webSocketStore.js
  3. 2 65
      src/views/im/chat/index.vue
  4. 100 23
      src/views/im/components/CallController/index.vue

+ 2 - 0
src/App.vue

@@ -9,10 +9,12 @@
   />
   <div class="main" :style="appMainViewStyle">
     <RouterView />
+    <CallController />
   </div>
 </template>
 
 <script setup>
+import CallController from "@/views/im/components/CallController/index.vue";
 import { checkAndUpdate } from "@/updater/index";
 import { getNotchHeight } from "@/utils/statusBar";
 import { setupNotifications } from "@/utils/notifications.js";

+ 50 - 0
src/stores/modules/webSocketStore.js

@@ -27,6 +27,7 @@ export const useWebSocketStore = defineStore("webSocketStore", {
     toUserInfo: {},
     unreadMessages: [], // 未读消息列表
     uuid: null,
+    onMessageCallbacks: [],
 
     // 心跳检测配置
     heartCheck: {
@@ -239,5 +240,54 @@ export const useWebSocketStore = defineStore("webSocketStore", {
     dealWebRtcMessage(message) {
       // 实现WebRTC消息处理逻辑
     },
+    handleMessage(data) {
+      const MessageType = protobuf.lookupType("protocol.Message");
+      const reader = new FileReader();
+
+      reader.onload = (event) => {
+        try {
+          const messagePB = MessageType.decode(new Uint8Array(event.target?.result));
+          const message = MessageType.toObject(messagePB, {
+            longs: String,
+            enums: String,
+            bytes: String,
+          });
+
+          console.log("收到消息:", message);
+          handleMessageHook(message, this);
+
+          // 调用所有回调函数
+          this.onMessageCallbacks.forEach(cb => {
+            try {
+              cb(message);
+            } catch (e) {
+              console.error("消息回调错误:", e);
+            }
+          });
+
+          if (message.type === "heatbeat") return;
+
+          if (message.type === Constant.MESSAGE_TRANS_TYPE) {
+            this.dealWebRtcMessage(message);
+            return;
+          }
+          // 其他消息处理逻辑...
+        } catch (error) {
+          console.error("消息解码错误:", error);
+        }
+      };
+
+      reader.readAsArrayBuffer(data);
+    },
+
+    addOnMessageCallback(cb) {
+      if (typeof cb === "function") {
+        this.onMessageCallbacks.push(cb);
+      }
+    },
+
+    removeOnMessageCallback(cb) {
+      this.onMessageCallbacks = this.onMessageCallbacks.filter(item => item !== cb);
+    },
   },
 });

+ 2 - 65
src/views/im/chat/index.vue

@@ -167,15 +167,6 @@
         </div>
       </div>
     </div>
-    <CallController
-      :visible="rtcStore.imSate.videoCallModal"
-      :inCall="inCall"
-      :callName="rtcStore.imSate.callName"
-      :callAvatar="rtcStore.imSate.callAvatar"
-      @accept="acceptCall"
-      @reject="rejectCall"
-      @hangup="hangupCall"
-    ></CallController>
     <!-- 来电弹窗 -->
     <!-- <div v-if="rtcStore.imSate.videoCallModal && !inCall" class="call-modal">
       <p>{{ rtcStore.imSate.callName }} 正在呼叫你</p>
@@ -202,7 +193,6 @@ import { MSG_TYPE, MESSAGE_TYPE_USER } from "@/common/constant/msgType";
 import messageAudio from "@/views/im/components/messageAudio/index.vue";
 import { showToast, showImagePreview } from "vant";
 import { useWebRTCStore } from "@/stores/modules/webrtcStore";
-import CallController from "@/views/im/components/CallController/index.vue";
 import * as Constant from "@/common/constant/Constant";
 
 const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE;
@@ -224,13 +214,6 @@ const appBoxHeight = ref(210);
 const chatListRef = ref(null);
 const emojiRef = ref(null);
 const toolsRef = ref(null);
-const inCall = ref(false); // 是否处于通话中
-
-// 示例用户
-const currentUser = ref({
-  avatar: "https://example.com/avatar.jpg",
-  nickname: "张三",
-});
 
 const isSender = (toUsername) => {
   return walletStore.account === toUsername;
@@ -443,9 +426,10 @@ const beforeRead = (file) => {
 //  创建呼叫:开启语音电话: 调试用,正式逻辑读src/views/im/hook/messagesHook.js
 // ==== 1. 发起语音通话 ====
 const startAudioOnline = async () => {
-  inCall.value = true;
   rtcStore.imSate.videoCallModal = true;
   rtcStore.imSate.callAvatar = wsStore.toUserInfo.avatar;
+  rtcStore.imSate.callName = wsStore.toUserInfo.nickname;
+  rtcStore.isCaller = true;
   wsStore.sendMessage({
     messageType: MESSAGE_TYPE_USER, // 单聊消息
     contentType: Constant.DIAL_AUDIO_ONLINE,
@@ -453,53 +437,6 @@ const startAudioOnline = async () => {
     avatar: walletStore.avatar,
   });
 };
-// ==== 2. 接听来电 ====
-async function acceptCall() {
-  try {
-    // 标记为通话中状态
-    inCall.value = true;
-
-    // 发送接听消息给对方
-    wsStore.sendMessage({
-      messageType: MESSAGE_TYPE_USER, // 单聊消息
-      contentType: Constant.ACCEPT_AUDIO_ONLINE, // 接听语音通话的消息类型
-      type: Constant.MESSAGE_TRANS_TYPE, // webrtc类型
-    });
-
-    // 这里你可以加上开启 WebRTC 连接或媒体流处理逻辑
-    // 例如调用 rtcStore 的方法,启动本地音频/视频流
-    // await rtcStore.startLocalAudio();
-  } catch (error) {
-    console.error("接听失败", error);
-    // 如果接听失败,考虑重置状态或提示用户
-    inCall.value = false;
-  }
-}
-
-// ==== 3. 拒接来电 ====
-function rejectCall() {
-  wsStore.sendMessage({
-    messageType: MESSAGE_TYPE_USER, // 单聊消息
-    contentType: Constant.REJECT_AUDIO_ONLINE,
-    type: Constant.MESSAGE_TRANS_TYPE,
-  });
-  // 隐藏弹窗
-  rtcStore.imSate.videoCallModal = false;
-  inCall.value = false;
-}
-
-// ==== 4. 挂断通话 ====
-function hangupCall() {
-  rtcStore.cleanup();
-  // 隐藏弹窗
-  rtcStore.imSate.videoCallModal = false;
-  inCall.value = false;
-  wsStore.sendMessage({
-    messageType: MESSAGE_TYPE_USER, // 单聊消息
-    contentType: Constant.CANCELL_AUDIO_ONLINE,
-    type: Constant.MESSAGE_TRANS_TYPE,
-  });
-}
 
 // 时间格式化
 const formatTime = (timestamp) => {

+ 100 - 23
src/views/im/components/CallController/index.vue

@@ -1,46 +1,123 @@
 <template>
-    <div v-if="visible" class="weixin-call-modal">
+    <div v-if="rtcStore.imSate.videoCallModal" class="weixin-call-modal">
       <div class="caller-info">
-        <img class="avatar" :src="callAvatar || defaultAvatar" alt="头像" />
-        <div class="name">{{ callName || '未知用户' }}</div>
+        <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="!inCall">
-          <button class="btn reject" @click="$emit('reject')">拒绝</button>
-          <button class="btn accept" @click="$emit('accept')">接听</button>
+        <!-- 拨打方,只显示挂断按钮 -->
+        <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="$emit('hangup')">挂断</button>
+          <button class="btn hangup" @click="hangupCall">挂断</button>
         </template>
       </div>
     </div>
   </template>
   
-  <script setup> 
   
-  const props = defineProps({
-    visible: Boolean,           // 是否显示弹窗
-    inCall: Boolean,            // 是否通话中
-    callName: String,           // 来电人姓名
-    callAvatar: String,         // 来电人头像
-  });
+  <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 (!props.inCall) {
-      return "正在语音通话请求...";
-    } else {
-      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;
+        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);
+      inCall.value = false;
     }
+  }
+  
+  function rejectCall() {
+    wsStore.sendMessage({
+      messageType: Constant.MESSAGE_TYPE_USER,
+      contentType: Constant.REJECT_AUDIO_ONLINE,
+      type: Constant.MESSAGE_TRANS_TYPE,
+    });
+    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,
+    });
+    rtcStore.imSate.videoCallModal = false;
+    inCall.value = false;
+  }
+  
+  onMounted(() => {
+    wsStore.onMessageCallbacks.push(onMessage);
+  });
+  
+  onBeforeUnmount(() => {
+    wsStore.onMessageCallbacks = wsStore.onMessageCallbacks.filter(
+      (cb) => cb !== onMessage
+    );
   });
   </script>
   
-  <style scoped>
+  <style scoped lang="less">
   .weixin-call-modal {
     position: fixed;
     inset: 0;
@@ -101,7 +178,7 @@
   }
   
   .btn.accept {
-    background-color: #4cd964; /* 绿色接听 */
+    background-color: #4cd964;
   }
   
   .btn.accept:hover {
@@ -109,7 +186,7 @@
   }
   
   .btn.reject {
-    background-color: #ff3b30; /* 红色拒绝 */
+    background-color: #ff3b30;
   }
   
   .btn.reject:hover {