|
@@ -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">
|