bzkf3 2 lat temu
rodzic
commit
8f0cf55d6b

+ 1 - 0
package.json

@@ -19,6 +19,7 @@
     "@element-plus/icons-vue": "^2.0.10",
     "@vueuse/components": "^9.10.0",
     "@vueuse/core": "^9.10.0",
+    "agora-rtc-sdk-ng": "^4.16.0",
     "axios": "^1.2.2",
     "element-plus": "^2.2.28",
     "tinymce": "^6.3.1",

+ 12 - 0
pnpm-lock.yaml

@@ -9,6 +9,7 @@ specifiers:
   '@vueuse/components': ^9.10.0
   '@vueuse/core': ^9.10.0
   '@windicss/plugin-scrollbar': ^1.2.3
+  agora-rtc-sdk-ng: ^4.16.0
   axios: ^1.2.2
   element-plus: ^2.2.28
   eslint: ^8.31.0
@@ -31,6 +32,7 @@ dependencies:
   '@element-plus/icons-vue': 2.0.10_vue@3.2.45
   '@vueuse/components': 9.10.0_vue@3.2.45
   '@vueuse/core': 9.10.0_vue@3.2.45
+  agora-rtc-sdk-ng: 4.16.0
   axios: 1.2.2
   element-plus: 2.2.28_vue@3.2.45
   tinymce: 6.3.1
@@ -908,6 +910,16 @@ packages:
     hasBin: true
     dev: true
 
+  /agora-rtc-sdk-ng/4.16.0:
+    resolution: {integrity: sha512-Wyoyzb0+ewRfMtyDxnJqHVQqBQYO4IkZsmlfzomXt69FA1rAr5yhQBxy1LxO/mwE9mm1XIC/9Yz5dEjL2d4ehg==}
+    dependencies:
+      agora-rte-extension: 1.2.3
+    dev: false
+
+  /agora-rte-extension/1.2.3:
+    resolution: {integrity: sha512-k3yNrYVyzJRoQJjaJUktKUI1XRtf8J1XsW8OzYKFqGlS8WQRMsES1+Phj2rfuEriiLObfuyuCimG6KHQCt5tiw==}
+    dev: false
+
   /ajv/6.12.6:
     resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
     dependencies:

+ 0 - 25
src/components/chat-audio/index.vue

@@ -1,25 +0,0 @@
-<script setup lang="ts">
-import { UseDraggable } from '@vueuse/components'
-
-let isOpen = $ref<boolean>(false)
-
-defineExpose({
-  open() {
-    isOpen = true
-  },
-  close() {
-    isOpen = false
-  }
-})
-</script>
-
-<template>
-  <UseDraggable v-if="isOpen" storage-key="chat-audio" storage-type="session" :initial-value="{ x: 584, y: 207 }"
-    class="fixed w-375px h-670px bg-hex-191919 cursor-move z-4000">
-    <slot></slot>
-  </UseDraggable>
-</template>
-
-<style scoped>
-
-</style>

+ 1 - 1
src/components/chat-stu-card/index.vue

@@ -12,7 +12,7 @@ const showMsg = $computed(() => {
 
 <template>
   <div class="h-full flex w-full items-center space-x-4 cursor-pointer">
-    <el-avatar :size="48"></el-avatar>
+    <el-avatar :size="48" :src="d.dxx_user_avatar"></el-avatar>
     <div class="flex flex-col justify-evenly flex-auto">
       <div class="flex justify-between">
         <div class="font-medium">{{ d.dxz_stu_user_realname }}</div>

+ 1 - 1
src/components/info-item/index.vue

@@ -18,7 +18,7 @@ const showMsg = decodeURIComponent(props.d.dxzl_last_msg_content!)
 <template>
   <div class="w-full" :class="left ? 'left' : 'right'">
     <div class="flex items-end r_flex-row-reverse gap-4">
-      <div>{{ id2name[d.create_user_id] }} {{ d.dxzl_id }}</div>
+      <div>{{ id2name[d.create_user_id] }}</div>
       <div class="text-sm text-hex-AFB2B6">{{ formatTimestamp(d.create_dateline) }}</div>
     </div>
     <div class="flex mt-2 r_flex-row-reverse">

+ 6 - 5
src/components/tinymce-area/index.vue

@@ -18,7 +18,7 @@ const props = defineProps({
   },
 })
 
-const emits = defineEmits(['update:modelValue'])
+const emits = defineEmits(['update:modelValue', 'click:audit', 'click:video'])
 
 const content = ref('')
 
@@ -104,15 +104,15 @@ onBeforeUnmount(() => {
 defineExpose({
   clear(v = '') {
     editor.resetContent(v)
-  }
+  },
 })
 
 function handleClickAuditCall() {
-
+  emits('click:audit')
 }
 
 function handleClickVideoCall() {
-
+  emits('click:video')
 }
 </script>
 
@@ -127,7 +127,8 @@ function handleClickVideoCall() {
       </div>
       <div @click="handleClickVideoCall"
         class="cursor-pointer flex_center box-content w-28px px-3px h-full  rounded-sm hover:bg-hex-cce2fa">
-        <i:wpf:video-call class=" w-6 h-6" /></div>
+        <i:wpf:video-call class=" w-6 h-6" />
+      </div>
     </div>
   </div>
 </template>

+ 176 - 3
src/pages/student/consult.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import AgoraRTC from "agora-rtc-sdk-ng"
+import type { IAgoraRTCClient, IMicrophoneAudioTrack, ICameraVideoTrack } from "agora-rtc-sdk-ng"
 import type { type_dyaw_xlfw_zxhd, type_dyaw_xlfw_zxhd_log } from '~/types';
 import user from '~/store/user';
 import { createSocket, socketSend } from '~/utils/ws';
@@ -19,6 +21,7 @@ let teacherInfo = $ref<{
   dxp_user_phone: string;
   dxp_wx_qrcode: string;
   dxp_jj: string;
+  dxp_user_avatar: string;
 }>()
 request({
   url: '/dyaw/xlfw_pbgl/detail',
@@ -41,7 +44,6 @@ const dyaw_xlfw_zxhd: type_dyaw_xlfw_zxhd = (await request({
   }
 })).data.one_info
 
-const ChatAudioRef = $ref<typeof import('~/components/chat-audio/index.vue')['default']>()
 let rateDialogVisible = $ref(false)
 let endTime = $ref<string>()
 let rateNum = $ref(5)
@@ -155,6 +157,110 @@ watch(
   }
 )
 
+// ==========
+// chat audit/video
+// ==========
+
+let rtcInstance: {
+  client?: IAgoraRTCClient;
+  localAudioTrack?: IMicrophoneAudioTrack;
+  localVideoTrack?: ICameraVideoTrack
+} = {
+  client: undefined,
+  localAudioTrack: undefined,
+  localVideoTrack: undefined,
+}
+
+const ws2 = createSocket(
+  { teacher: teacher.user_id, student: '*' },
+  {
+    message(socketRes: TSocketRes<type_dyaw_xlfw_zxhd_log & { operate: CHAT_OPERATION }>) {
+      if (socketRes.from_client_name.endsWith('teacher')) {
+        // infoList.push(socketRes.content)
+        if (socketRes.content.dxzl_stu_user_id === user.user_id) {
+          switch (socketRes.content.operate) {
+            case CHAT_OPERATION.START:
+              auditChatStatus = CHAT_STATUS.WAITING_YOU_ACCEPT
+              ChatAudioRef!.open()
+              break;
+            case CHAT_OPERATION.CANCEL:
+              ChatAudioRef!.close()
+              break;
+            case CHAT_OPERATION.ACCEPT:
+              auditChatStatus = CHAT_STATUS.CHATING
+              break;
+            case CHAT_OPERATION.DENY:
+              ChatAudioRef!.close()
+              break;
+            case CHAT_OPERATION.END:
+              ChatAudioRef!.close()
+              break;
+            default:
+              break;
+          }
+        }
+      }
+    }
+  }
+)
+
+enum CHAT_STATUS { 'WAITING_YOU_ACCEPT', 'WAITING_OTHERS_ACCEPT', 'WAITING_BUSY', 'CHATING' }
+enum CHAT_OPERATION { 'START', 'CANCEL', 'ACCEPT', 'DENY', 'END' }
+let auditChatStatus = $ref<CHAT_STATUS>(CHAT_STATUS.WAITING_OTHERS_ACCEPT)
+
+const ChatAudioRef = $ref<typeof import("~/components/chat-dialog/index.vue")['default']>()
+const ChatVideoRef = $ref<typeof import("~/components/chat-dialog/index.vue")['default']>()
+
+function handleAuditChatStart() {
+  ChatAudioRef!.open()
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.START
+  })
+}
+function handleAuditChatCancel() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.CANCEL
+  })
+  ChatAudioRef!.close()
+}
+function handleAuditChatAccept() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.ACCEPT
+  })
+  // ChatAudioRef!.close()
+  auditChatStatus = CHAT_STATUS.CHATING
+}
+function handleAuditChatDeny() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.DENY
+  })
+  ChatAudioRef!.close()
+}
+function handleAuditChatEnd() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.END
+  })
+  ChatAudioRef!.close()
+}
+function handleVideoChatStart() {
+  request({
+    url: '/dyaw/xlfw_zxhd/get_rtc_token',
+    data: {
+      dxz_id: dyaw_xlfw_zxhd?.dxz_id
+    }
+  })
+  // ChatVideoRef!.open()
+}
 </script>
 
 <template>
@@ -174,7 +280,8 @@ watch(
       <div class="bg-white h-180px p-5px flex flex-col justify-between">
         <!-- <div class="h-48px"></div>
         <el-input type="textarea"></el-input> -->
-        <tinymce-area v-model="inputValue" ref="TinyRef"></tinymce-area>
+        <tinymce-area v-model="inputValue" ref="TinyRef" @click:audit="handleAuditChatStart"
+          @click:video="handleVideoChatStart"></tinymce-area>
         <div class="flex justify-end">
           <div class="bg-pink-300 text-sm text-white w-80px h-32px flex_center rounded-2xl cursor-pointer"
             @click="handleClickSend">发送</div>
@@ -216,7 +323,73 @@ watch(
 
   </el-dialog>
 
-  <!-- <chat-audio ref="ChatAudioRef"></chat-audio> -->
+
+  <chat-dialog ref="ChatAudioRef">
+    <div class="h-full flex_center flex-col text-light-50 space-y-4">
+      <el-avatar :size="158" :src="teacherInfo?.dxp_user_avatar"></el-avatar>
+      <div>{{ teacherInfo?.dxp_user_realname }}</div>
+      <!-- <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"> -->
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"
+        v-show="auditChatStatus === CHAT_STATUS.WAITING_OTHERS_ACCEPT">
+        <div>正在等待对方接受邀请</div>
+        <i:line-md:loading-alt-loop class="text-xl" />
+      </div>
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"
+        v-show="auditChatStatus === CHAT_STATUS.WAITING_YOU_ACCEPT">
+        <div>邀请你语音通话...</div>
+      </div>
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"
+        v-show="auditChatStatus === CHAT_STATUS.WAITING_BUSY">
+        <div class="text-red-500">对方忙线中请等待</div>
+        <div class="text-red-500">当前排队:{{ 4 }}</div>
+        <i:line-md:loading-alt-loop class="text-xl" />
+      </div>
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16" v-show="auditChatStatus === CHAT_STATUS.CHATING">
+        <div>正在通话中</div>
+        <div>{{ '00:30' }}</div>
+      </div>
+      <!-- </div> -->
+
+      <div class="pt-16 text-xl flex justify-around w-full">
+        <div v-show="auditChatStatus === CHAT_STATUS.WAITING_YOU_ACCEPT"
+          class="bg-green-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatAccept">
+          <i:ic:baseline-phone />
+        </div>
+        <div v-show="auditChatStatus === CHAT_STATUS.WAITING_YOU_ACCEPT"
+          class="bg-red-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatDeny">
+          <i:mdi:phone-hangup />
+        </div>
+        <div v-show="auditChatStatus === CHAT_STATUS.CHATING"
+          class="bg-hex-efefef text-hex-272636 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="">
+          <i:ant-design:audio-outlined v-show="true" />
+          <i:ant-design:audio-muted-outlined v-show="false" />
+        </div>
+        <div v-show="auditChatStatus === CHAT_STATUS.CHATING"
+          class="bg-red-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatEnd">
+          <i:ic:outline-close></i:ic:outline-close>
+        </div>
+        <div
+          v-show="auditChatStatus === CHAT_STATUS.WAITING_OTHERS_ACCEPT || auditChatStatus === CHAT_STATUS.WAITING_BUSY"
+          class="bg-red-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatCancel">
+          <i:ic:outline-close></i:ic:outline-close>
+        </div>
+
+      </div>
+    </div>
+  </chat-dialog>
+
+  <chat-dialog ref="ChatVideoRef">
+    <i:ant-design:audio-outlined />
+    <i:ant-design:audio-muted-outlined />
+
+    <i:material-symbols:video-camera-back-rounded />
+    <i:material-symbols:video-camera-front-off-rounded />
+  </chat-dialog>
 </template>
 
 <style scoped lang="scss">

+ 1 - 1
src/pages/student/home.vue

@@ -31,7 +31,7 @@ const teacherList = (await request({
 
 <template>
   <div class="h-640px bg-hex-f2f2f295 flex justify-center">
-    <div class="w-1000px h-full">
+    <div class="w-1000px h-full overflow-hidden">
       <img v-show="imgSrc" :src="imgSrc" class="w-full h-full object-contain">
     </div>
     <div

+ 269 - 4
src/pages/teacher/consult.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import AgoraRTC from "agora-rtc-sdk-ng"
+import type { IAgoraRTCClient, IMicrophoneAudioTrack, ICameraVideoTrack } from "agora-rtc-sdk-ng"
 import type { type_dyaw_xlfw_zxhd, type_dyaw_xlfw_zxhd_log, type_archives_item } from '~/types';
 import { Search } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -101,7 +103,7 @@ function handleClickStuCard(stu: type_dyaw_xlfw_zxhd) {
     url: '/dyaw/xlfw_zxhd_log/index',
     data: {
       dxzl_stu_user_id: stu.dxz_stu_user_id,
-      dxzl_tea_user_id: user.user_id,
+      dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
     }
   }).then(res => {
     if (res.code === '1') {
@@ -117,7 +119,7 @@ function handleClickStuCard(stu: type_dyaw_xlfw_zxhd) {
                 url: '/dyaw/xlfw_zxhd_log/index',
                 data: {
                   dxzl_stu_user_id: stu.dxz_stu_user_id,
-                  dxzl_tea_user_id: user.user_id,
+                  dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
                   limit: 0
                 }
               })
@@ -138,7 +140,7 @@ function handleLoadMoreInfo() {
     url: '/dyaw/xlfw_zxhd_log/index',
     data: {
       dxzl_stu_user_id: dyaw_xlfw_zxhd?.dxz_stu_user_id,
-      dxzl_tea_user_id: user.user_id,
+      dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
       zxhd_log_id: infoList[0].dxzl_id
     }
   }).then(res => {
@@ -211,6 +213,201 @@ function handleQueryArchives() {
     }
   })
 }
+
+// ==========
+// chat audit/video
+// ==========
+let rtcInstance: {
+  client?: IAgoraRTCClient;
+  localAudioTrack?: IMicrophoneAudioTrack;
+  localVideoTrack?: ICameraVideoTrack
+} = {
+  client: undefined,
+  localAudioTrack: undefined,
+  localVideoTrack: undefined,
+}
+
+
+const ws2 = createSocket(
+  { teacher: user.user_id, student: '*' },
+  {
+    message(socketRes: TSocketRes<type_dyaw_xlfw_zxhd_log & { operate: CHAT_OPERATION }>) {
+      if (socketRes.from_client_name.endsWith('student')) {
+        // infoList.push(socketRes.content)
+        console.log('');
+        if (socketRes.content.dxzl_tea_user_id === user.user_id) {
+          console.log('operate:', socketRes.content.operate);
+          switch (socketRes.content.operate) {
+            case CHAT_OPERATION.START:
+              auditChatStatus = CHAT_STATUS.WAITING_YOU_ACCEPT
+              ChatAudioRef!.open()
+              break;
+            case CHAT_OPERATION.CANCEL:
+              ChatAudioRef!.close()
+              break;
+            case CHAT_OPERATION.ACCEPT:
+              auditChatStatus = CHAT_STATUS.CHATING
+              break;
+            case CHAT_OPERATION.DENY:
+              ChatAudioRef!.close()
+              break;
+            case CHAT_OPERATION.END:
+              ChatAudioRef!.close()
+              break;
+            default:
+              break;
+          }
+        }
+      }
+    }
+  }
+)
+
+enum CHAT_STATUS { 'WAITING_YOU_ACCEPT', 'WAITING_OTHERS_ACCEPT', 'WAITING_BUSY', 'CHATING' }
+enum CHAT_OPERATION { 'START', 'CANCEL', 'ACCEPT', 'DENY', 'END' }
+let auditChatStatus = $ref<CHAT_STATUS>(CHAT_STATUS.WAITING_OTHERS_ACCEPT)
+
+const ChatAudioRef = $ref<typeof import("~/components/chat-dialog/index.vue")['default']>()
+const ChatVideoRef = $ref<typeof import("~/components/chat-dialog/index.vue")['default']>()
+
+
+async function handleAuditChatStart() {
+  try {
+    let rtcOptions
+    await request({
+      url: '/dyaw/xlfw_zxhd/get_rtc_token',
+      data: {
+        dxz_id: dyaw_xlfw_zxhd?.dxz_id
+      }
+    }).then(async res => {
+      if (res.code === '1') {
+        let resp: { jgim_roomid: string; rtc_appid: string; rtc_token: string } = res.data.one_info
+        rtcOptions = {
+          appId: resp.rtc_appid,
+          channel: resp.jgim_roomid,
+          token: resp.rtc_token,
+          uid: user.user_id
+        }
+
+        let client = rtcInstance.client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
+
+        client.on("user-published", async (user, mediaType) => {
+          // 发起订阅
+          await client.subscribe(user, mediaType);
+
+          // 如果订阅的是音频轨道
+          if (mediaType === "audio") {
+            const audioTrack = user.audioTrack;
+            // 自动播放音频
+            audioTrack?.play();
+          } else {
+            const videoTrack = user.videoTrack;
+            // 自动播放视频
+            videoTrack?.play(RemotePlayerContainerRef as HTMLElement);
+          }
+        });
+
+        await rtcInstance.client.join(rtcOptions.appId, rtcOptions.channel, rtcOptions?.token, rtcOptions.uid);
+        rtcInstance.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
+        // rtcInstance.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
+        await rtcInstance.client.publish([rtcInstance.localAudioTrack]);
+        // rtcInstance.localVideoTrack.play(LocalPlayerContainerRef as HTMLElement);
+      }
+    })
+    ChatAudioRef!.open()
+    socketSend(ws2, {
+      dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+      dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+      operate: CHAT_OPERATION.START,
+      rtcOptions
+    })
+  } catch (error) {
+    console.error(error);
+  }
+
+}
+function handleAuditChatCancel() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.CANCEL
+  })
+  ChatAudioRef!.close()
+}
+function handleAuditChatAccept() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.ACCEPT
+  })
+  // ChatAudioRef!.close()
+  auditChatStatus = CHAT_STATUS.CHATING
+}
+function handleAuditChatDeny() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.DENY
+  })
+  ChatAudioRef!.close()
+}
+function handleAuditChatEnd() {
+  socketSend(ws2, {
+    dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
+    dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+    operate: CHAT_OPERATION.END
+  })
+  ChatAudioRef!.close()
+}
+
+
+const LocalPlayerContainerRef = $ref<HTMLElement>()
+const RemotePlayerContainerRef = $ref<HTMLElement>()
+async function handleVideoChatStart() {
+  await request({
+    url: '/dyaw/xlfw_zxhd/get_rtc_token',
+    data: {
+      dxz_id: dyaw_xlfw_zxhd?.dxz_id
+    }
+  }).then(async res => {
+    if (res.code === '1') {
+      let resp: { jgim_roomid: string; rtc_appid: string; rtc_token: string } = res.data.one_info
+      const rtcOptions = {
+        appId: resp.rtc_appid,
+        channel: resp.jgim_roomid,
+        token: resp.rtc_token,
+        uid: user.user_id
+      }
+
+      let client = rtcInstance.client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
+
+      client.on("user-published", async (user, mediaType) => {
+        // 发起订阅
+        await client.subscribe(user, mediaType);
+
+        // 如果订阅的是音频轨道
+        if (mediaType === "audio") {
+          const audioTrack = user.audioTrack;
+          // 自动播放音频
+          audioTrack?.play();
+        } else {
+          const videoTrack = user.videoTrack;
+          // 自动播放视频
+          videoTrack?.play(RemotePlayerContainerRef as HTMLElement);
+        }
+      });
+
+      await rtcInstance.client.join(rtcOptions.appId, rtcOptions.channel, rtcOptions?.token, rtcOptions.uid);
+      rtcInstance.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
+      rtcInstance.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
+      await rtcInstance.client.publish([rtcInstance.localAudioTrack, rtcInstance.localVideoTrack]);
+      rtcInstance.localVideoTrack.play(LocalPlayerContainerRef as HTMLElement);
+    }
+  })
+  ChatVideoRef!.open()
+}
+
+let RtcDialogRef = $ref<typeof import("~/components/rtc-dialog/index.vue")['default']>()
 </script>
 
 <template>
@@ -238,7 +435,8 @@ function handleQueryArchives() {
             :d="item"></info-item>
         </div>
         <div class="bg-white h-180px p-5px flex flex-col justify-between">
-          <tinymce-area v-model="inputValue" ref="TinyRef"></tinymce-area>
+          <tinymce-area v-model="inputValue" ref="TinyRef" @click:audit="handleAuditChatStart"
+            @click:video="handleVideoChatStart"></tinymce-area>
           <div class="flex justify-end">
             <div class="bg-pink-300 text-sm text-white w-80px h-32px flex_center rounded-2xl cursor-pointer"
               @click="handleClickSend">发送</div>
@@ -274,6 +472,73 @@ function handleQueryArchives() {
     </div>
   </div>
 
+  <chat-dialog ref="ChatAudioRef">
+    <div class="h-full flex_center flex-col text-light-50 space-y-4">
+      <el-avatar :size="158" :src="dyaw_xlfw_zxhd?.dxx_user_avatar"></el-avatar>
+      <div>{{ dyaw_xlfw_zxhd?.dxz_stu_user_realname }}</div>
+      <!-- <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"> -->
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"
+        v-show="auditChatStatus === CHAT_STATUS.WAITING_OTHERS_ACCEPT">
+        <div>正在等待对方接受邀请</div>
+        <i:line-md:loading-alt-loop class="text-xl" />
+      </div>
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"
+        v-show="auditChatStatus === CHAT_STATUS.WAITING_YOU_ACCEPT">
+        <div>邀请你语音通话...</div>
+      </div>
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16"
+        v-show="auditChatStatus === CHAT_STATUS.WAITING_BUSY">
+        <div class="text-red-500">对方忙线中请等待</div>
+        <div class="text-red-500">当前排队:{{ 4 }}</div>
+        <i:line-md:loading-alt-loop class="text-xl" />
+      </div>
+      <div class="text-hex-909090 flex_center flex-col space-y-2 h-16" v-show="auditChatStatus === CHAT_STATUS.CHATING">
+        <div>正在通话中</div>
+        <div>{{ '00:30' }}</div>
+      </div>
+      <!-- </div> -->
+
+      <div class="pt-16 text-xl flex justify-around w-full">
+        <div v-show="auditChatStatus === CHAT_STATUS.WAITING_YOU_ACCEPT"
+          class="bg-green-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatAccept">
+          <i:ic:baseline-phone />
+        </div>
+        <div v-show="auditChatStatus === CHAT_STATUS.WAITING_YOU_ACCEPT"
+          class="bg-red-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatDeny">
+          <i:mdi:phone-hangup />
+        </div>
+        <div v-show="auditChatStatus === CHAT_STATUS.CHATING"
+          class="bg-hex-efefef text-hex-272636 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="">
+          <i:ant-design:audio-outlined v-show="true" />
+          <i:ant-design:audio-muted-outlined v-show="false" />
+        </div>
+        <div v-show="auditChatStatus === CHAT_STATUS.CHATING"
+          class="bg-red-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatEnd">
+          <i:ic:outline-close></i:ic:outline-close>
+        </div>
+        <div
+          v-show="auditChatStatus === CHAT_STATUS.WAITING_OTHERS_ACCEPT || auditChatStatus === CHAT_STATUS.WAITING_BUSY"
+          class="bg-red-600 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
+          @click="handleAuditChatCancel">
+          <i:ic:outline-close></i:ic:outline-close>
+        </div>
+
+      </div>
+    </div>
+
+  </chat-dialog>
+
+  <chat-dialog>
+    <div ref="LocalPlayerContainerRef"></div>
+    <div ref="RemotePlayerContainerRef"></div>
+  </chat-dialog>
+
+  <rtc-dialog ref="RtcDialogRef"></rtc-dialog>
+
 </template>
 
 <style scoped lang="scss">

+ 1 - 1
src/pages/teacher/home.vue

@@ -8,7 +8,7 @@ const imgSrc = (await request({
 </script>
 
 <template>
-  <div class="w-full max-h-590px flex_center">
+  <div class="w-full max-h-590px flex_center overflow-hidden">
     <img v-show="imgSrc" :src="imgSrc" class="w-full h-full object-contain">
   </div>
 </template>

+ 3 - 1
src/pages/teacher/index.vue

@@ -42,7 +42,9 @@ function routerPush(name: string) {
   item.title
         }}</div>
     </div>
-    <router-view class="flex-auto"></router-view>
+    <div class="flex-auto">
+      <router-view></router-view>
+    </div>
   </div>
 </template>
 

Plik diff jest za duży
+ 2 - 336
src/store/user.store.ts


+ 2 - 0
src/store/user.ts

@@ -2,6 +2,7 @@ import { IUser } from './user.d';
 // #ifdef DEV
 import dictionary from './user.store';
 console.log('port :>> ', location.port);
+console.log('dictionary[location.port] :>> ', dictionary[location.port]);
 localStorage.setItem(
   "userInfo",
   JSON.stringify(
@@ -11,6 +12,7 @@ localStorage.setItem(
 // #endif
 
 let user = reactive<IUser>(JSON.parse(localStorage.getItem('userInfo') as string))
+console.log('user :>> ', user);
 export default user
 
 const ROLE_MAP: { [key: string]: 'teacher' | 'student' } = { '72': 'teacher', '75': 'teacher', '76': 'student' }

+ 6 - 1
src/types.ts

@@ -1,6 +1,8 @@
 // import type { ReactiveVariable } from 'vue';
 
 export type type_dyaw_xlfw_zxhd = {
+  dxx_user_avatar: string;
+  dxx_tea_avatar:string;
   dxz_stu_user_id: string;
   dxz_stu_user_realname: string;
   dxz_tea_user_id: string;
@@ -18,7 +20,7 @@ export type type_dyaw_xlfw_zxhd = {
 
 
 export type type_dyaw_xlfw_zxhd_log = {
-  dxzl_id?:string;
+  dxzl_id?: string;
   dxz_id: string;
   dxzl_stu_user_id: string;
   dxzl_stu_user_realname: string;
@@ -46,3 +48,6 @@ export type type_archives_item = {
   dxxd_wxdj?: string;
   dxxd_cbfx?: string;
 }
+
+export enum CHAT_STATUS { 'WAITING_YOU_ACCEPT', 'WAITING_OTHERS_ACCEPT', 'WAITING_BUSY', 'CHATING' }
+export enum CHAT_OPERATION { 'START', 'CANCEL', 'ACCEPT', 'DENY', 'END' }

+ 2 - 2
src/utils/ws.ts

@@ -34,11 +34,11 @@ export function createSocket(
         }
 
         if (data.type === 'say') {
-          const content =  {
+          const content = {
             ...data,
             content: JSON.parse(decodeURIComponent(data.content))
           }
-          console.groupCollapsed(`|- Socket Say:`)
+          console.groupCollapsed(`├─ Socket Say:`)
           console.log(content);
           console.groupEnd();
 

+ 1 - 0
tsconfig.json

@@ -31,6 +31,7 @@
     "src/**/*.d.ts",
     "src/**/*.tsx",
     "src/**/*.vue",
+    "src/**/**/*.vue",
     "./auto-imports.d.ts"
   ],
   "references": [