|
@@ -0,0 +1,295 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import type { type_dyaw_xlfw_zxhd, type_dyaw_xlfw_zxhd_log } from '~/types';
|
|
|
+import user from '~/store/user';
|
|
|
+import { createSocket, socketSend } from '~/utils/ws';
|
|
|
+import type { TSocketRes } from '~/utils/ws';
|
|
|
+import { formatTimestamp } from '~/utils/time'
|
|
|
+import { CHAT_STATUS, CHAT_OPERATION } from '~/types';
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+let teacher
|
|
|
+const SessionConsultTeacher = sessionStorage.getItem('consult_teacher')
|
|
|
+if (SessionConsultTeacher !== null) {
|
|
|
+ teacher = JSON.parse(SessionConsultTeacher)
|
|
|
+} else {
|
|
|
+ router.back()
|
|
|
+}
|
|
|
+
|
|
|
+let teacherInfo = $ref<{
|
|
|
+ dxp_user_realname: string;
|
|
|
+ dxp_user_phone: string;
|
|
|
+ dxp_wx_qrcode: string;
|
|
|
+ dxp_jj: string;
|
|
|
+ dxp_user_avatar: string;
|
|
|
+}>()
|
|
|
+request({
|
|
|
+ url: '/dyaw/xlfw_pbgl/detail',
|
|
|
+ data: {
|
|
|
+ user_id: teacher.user_id
|
|
|
+ }
|
|
|
+}).then(res => {
|
|
|
+ if (res.code === '1') {
|
|
|
+ teacherInfo = res.data.one_info
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const dyaw_xlfw_zxhd: type_dyaw_xlfw_zxhd = (await request({
|
|
|
+ url: '/dyaw/xlfw_zxhd/add',
|
|
|
+ data: {
|
|
|
+ dyaw_xlfw_zxhd: {
|
|
|
+ dxz_stu_user_id: user.user_id,
|
|
|
+ dxz_tea_user_id: teacher.user_id
|
|
|
+ }
|
|
|
+ }
|
|
|
+})).data.one_info
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+let rateDialogVisible = $ref(false)
|
|
|
+let endTime = $ref<string>()
|
|
|
+let rateNum = $ref(5)
|
|
|
+function handleClickEnd() {
|
|
|
+ // ChatAudioRef && ChatAudioRef.open()
|
|
|
+ endTime = getDatabaseTime(new Date())
|
|
|
+ rateDialogVisible = true
|
|
|
+}
|
|
|
+function handleConfirmRate() {
|
|
|
+ request({
|
|
|
+ url: '/dyaw/xlfw_zxhd/edit',
|
|
|
+ data: {
|
|
|
+ dxz_id: dyaw_xlfw_zxhd.dxz_id,
|
|
|
+ dyaw_xlfw_zxhd: {
|
|
|
+ dxz_star: rateNum,
|
|
|
+ dxz_star_datetime: endTime,
|
|
|
+ // 下行代码【可能】导致使用问题
|
|
|
+ dxz_status: '1'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === '1') {
|
|
|
+ rateDialogVisible = false
|
|
|
+ router.back()
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+let infoList = $ref<Array<type_dyaw_xlfw_zxhd_log>>([])
|
|
|
+let inputValue = $ref('')
|
|
|
+let isSending = $ref(false)
|
|
|
+let TinyRef = $ref<typeof import('~/components/tinymce-area/index.vue')['default']>()
|
|
|
+async function handleClickSend() {
|
|
|
+ if (isSending) return;
|
|
|
+ isSending = true
|
|
|
+ const reqDate = {
|
|
|
+ dxz_id: dyaw_xlfw_zxhd.dxz_id,
|
|
|
+ dxzl_stu_user_id: dyaw_xlfw_zxhd.dxz_stu_user_id,
|
|
|
+ dxzl_stu_user_realname: dyaw_xlfw_zxhd.dxz_stu_user_realname,
|
|
|
+ dxzl_tea_user_id: dyaw_xlfw_zxhd.dxz_tea_user_id,
|
|
|
+ dxzl_tea_user_realname: dyaw_xlfw_zxhd.dxz_tea_user_realname,
|
|
|
+ dxzl_last_msg_content: encodeURIComponent(inputValue),
|
|
|
+ dxzl_type: inputValue.includes('><img') ? '2' : '1'
|
|
|
+ }
|
|
|
+
|
|
|
+ // infoList.push({
|
|
|
+ // create_user_id: user.user_id,
|
|
|
+ // create_dateline: Date.now().toString().slice(0, 10),
|
|
|
+
|
|
|
+ // ...reqDate
|
|
|
+ // })
|
|
|
+ TinyRef?.clear()
|
|
|
+ // console.log('inputValue :>> ', inputValue);
|
|
|
+ request({
|
|
|
+ url: '/dyaw/xlfw_zxhd_log/add',
|
|
|
+ data: {
|
|
|
+ dyaw_xlfw_zxhd_log: reqDate
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === '1') {
|
|
|
+ const fullSendData = {
|
|
|
+ create_user_id: user.user_id,
|
|
|
+ create_dateline: Date.now().toString().slice(0, 10),
|
|
|
+ ...reqDate,
|
|
|
+ dxzl_id: `${res.data.insert_id}`
|
|
|
+ }
|
|
|
+ infoList.push(fullSendData)
|
|
|
+ socketSend(ws, fullSendData)
|
|
|
+ isSending = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+request({
|
|
|
+ url: '/dyaw/xlfw_zxhd_log/index',
|
|
|
+ data: {
|
|
|
+ dxz_id: dyaw_xlfw_zxhd.dxz_id,
|
|
|
+ limit: 100
|
|
|
+ }
|
|
|
+}).then(res => {
|
|
|
+ if (res.code === '1') {
|
|
|
+ infoList = res.data.page_data.reverse()
|
|
|
+ }
|
|
|
+})
|
|
|
+const ws = createSocket(
|
|
|
+ { teacher: teacher.user_id, student: user.user_id },
|
|
|
+ {
|
|
|
+ message(socketRes: TSocketRes<type_dyaw_xlfw_zxhd_log>) {
|
|
|
+ if (socketRes.from_client_name.endsWith('teacher')) {
|
|
|
+ infoList.push(socketRes.content)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+)
|
|
|
+// console.log('ws :>> ', ws);
|
|
|
+if (!dyaw_xlfw_zxhd.old_data) {
|
|
|
+
|
|
|
+ socketSend(ws, {
|
|
|
+ $: true,
|
|
|
+ dxz_stu_user_id: dyaw_xlfw_zxhd.dxz_stu_user_id,
|
|
|
+ dyaw_xlfw_zxhd
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const scrollbarRef = $ref<HTMLElement>()
|
|
|
+function scrollToBottom() {
|
|
|
+ if (!scrollbarRef) return;
|
|
|
+ const scrollHeight = scrollbarRef!.scrollHeight;
|
|
|
+ scrollbarRef!.scrollTo(0, scrollHeight);
|
|
|
+}
|
|
|
+watch(
|
|
|
+ () => (infoList),
|
|
|
+ () => {
|
|
|
+ nextTick(() => {
|
|
|
+ scrollToBottom()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ deep: true,
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// ==========
|
|
|
+// chat audio/video
|
|
|
+// ==========
|
|
|
+
|
|
|
+let RtcDialogRef = $ref<typeof import("~/components/rtc-dialog/index.vue")['default']>()
|
|
|
+
|
|
|
+const ws2 = createSocket(
|
|
|
+ { teacher: teacher.user_id, student: '*' },
|
|
|
+ {
|
|
|
+ message(socketRes: TSocketRes<type_dyaw_xlfw_zxhd_log & { operate: CHAT_OPERATION }>) {
|
|
|
+ console.log('enter', socketRes);
|
|
|
+ if (socketRes.from_client_name.endsWith('teacher')) {
|
|
|
+ if (socketRes.content.dxzl_stu_user_id === user.user_id) {
|
|
|
+ RtcDialogRef!.publisher(socketRes.content)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+)
|
|
|
+onMounted(() => {
|
|
|
+ RtcDialogRef!.init(ws2)
|
|
|
+})
|
|
|
+
|
|
|
+
|
|
|
+async function handleAudioChatStart() {
|
|
|
+ RtcDialogRef!.open(dyaw_xlfw_zxhd, 'audio')
|
|
|
+}
|
|
|
+
|
|
|
+async function handleVideoChatStart() {
|
|
|
+ RtcDialogRef!.open(dyaw_xlfw_zxhd, 'video')
|
|
|
+}
|
|
|
+
|
|
|
+function emitUpdateInfo(info: type_dyaw_xlfw_zxhd_log, isUpdate?: boolean) {
|
|
|
+ if (!isUpdate) {
|
|
|
+ if (info.dxz_id === dyaw_xlfw_zxhd?.dxz_id)
|
|
|
+ infoList.push(info)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const target = infoList.find(item => item.dxzl_id === info.dxzl_id)
|
|
|
+ if (target)
|
|
|
+ target.dxzl_last_msg_content = info.dxzl_last_msg_content
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 从⼼灵氧吧/梦⾥寻芳/微⻛细⾬/花⾹⻦语获取随机昵称
|
|
|
+function getRandomName() {
|
|
|
+ const names = ['⼼灵氧吧', '梦⾥寻芳', '微⻛细⾬', '花⾹⻦语']
|
|
|
+ return names[Math.floor(Math.random() * names.length)]
|
|
|
+}
|
|
|
+const virtualName = getRandomName()
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="h-640px bg-hex-f2f2f295 flex justify-center divide-x">
|
|
|
+ <div class="w-1000px h-full divide-y">
|
|
|
+ <div class="bg-pink-300 flex justify-between h-48px items-center px-18px">
|
|
|
+ <span class="text-lg px-1 tracking-wider">{{ `${dyaw_xlfw_zxhd.dxz_tea_user_realname}老师正在为您服务` }}</span>
|
|
|
+ <div class="text-pink-300 text-sm bg-white w-100px h-32px flex_center rounded-2xl cursor-pointer"
|
|
|
+ @click="handleClickEnd">结束会话</div>
|
|
|
+ </div>
|
|
|
+ <div ref="scrollbarRef"
|
|
|
+ class="bg-hex-fff8fb space-y-2 h-410px py-2 px-6 scrollbar scrollbar-thin scrollbar-thumb-rounded-md scrollbar-thumb-gray-200 scrollbar-track-transparent">
|
|
|
+ <div class="w-full text-center text-sm">{{ formatTimestamp(dyaw_xlfw_zxhd.create_dateline) }} 开始沟通</div>
|
|
|
+ <info-item v-for="item in infoList" :key="item.dxzl_id" :left="item.create_user_id !== user.user_id" :d="item"
|
|
|
+ :virtualName="virtualName" :w="780"></info-item>
|
|
|
+ </div>
|
|
|
+ <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" @click:audio="handleAudioChatStart"
|
|
|
+ @click:video="handleVideoChatStart" @click:submit="handleClickSend"></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>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="w-400px h-full space-y-4 bg-white overflow-y-auto flex flex-col items-stretch px-30px py-4 divide-y scrollbar scrollbar-thin scrollbar-thumb-rounded-md scrollbar-thumb-gray-200 scrollbar-track-transparent">
|
|
|
+
|
|
|
+ <template v-if="teacherInfo">
|
|
|
+ <div class="w-full">
|
|
|
+ <img :src="teacherInfo.dxp_wx_qrcode" alt="">
|
|
|
+ </div>
|
|
|
+ <div class="py-4">
|
|
|
+ <div class="text-lg font-bold max-w-24 truncate">{{ teacherInfo.dxp_user_realname }}</div>
|
|
|
+ <div class="text-hex-5B9FF0">联系电话:{{ teacherInfo.dxp_user_phone }}</div>
|
|
|
+ <div class=" text-hex-00000041 h-10 leading-5 mt-1">
|
|
|
+ {{ teacherInfo.dxp_jj }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-dialog v-model="rateDialogVisible" title="请评价" align-center width="405px">
|
|
|
+ <p>于 {{ endTime }} 结束沟通</p>
|
|
|
+ <div class="flex items-center my-4">
|
|
|
+ <div>评价:</div>
|
|
|
+ <el-rate size="large" v-model="rateNum"></el-rate>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex_center">
|
|
|
+ <el-button round type="primary" @click="handleConfirmRate">
|
|
|
+ 确认
|
|
|
+ </el-button>
|
|
|
+ <el-button round @click="rateDialogVisible = false">取消</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+
|
|
|
+ <rtc-dialog ref="RtcDialogRef" @update-info="emitUpdateInfo"></rtc-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">
|
|
|
+</style>
|