bzkf3 2 gadi atpakaļ
vecāks
revīzija
c0dfc59b8c

+ 1 - 1
public/course_health.html

@@ -20,7 +20,7 @@
   <!-- 公共头部 -->
   <div id="publicHeader"></div>
   <!-- 中间内容 -->
-  <div id="app"></div>
+  <div id="appx"></div>
   <script src="/config.js"></script>
   <!-- 公共底部 -->
   <div id="publicFooter"></div>

+ 6 - 0
src/App.vue

@@ -13,6 +13,12 @@ if (UserRole === 'other') {
   <el-config-provider :message="{ max: 3 }" :z-index="100" :locale="zhCn">
     <suspense>
       <div class="bg w-full h-672px flex justify-center items-center">
+        <!-- try to fix auto complete -->
+        <div class="hidden">
+          <input type="text">
+          <input type="text">
+          <input type="text">
+        </div>
         <router-view></router-view>
       </div>
 

+ 41 - 18
src/components/archives-card/index.vue

@@ -6,7 +6,8 @@ const props = defineProps<{
   disabled?: boolean;
   storage?: boolean;
 }>()
-const archivesStorage = sessionStorage.getItem(`a_${new Date().toLocaleDateString()}_${props.d.dxz_id}`)
+let Storagekey = `a_${new Date().toLocaleDateString()}_${props.d.dxz_id}`
+const archivesStorage = sessionStorage.getItem(Storagekey)
 const form = reactive<type_archives_item>(
   (props.storage && archivesStorage)
     ? JSON.parse(archivesStorage)
@@ -37,12 +38,25 @@ defineExpose({
 watch(
   form,
   (val) => {
-    props.storage && sessionStorage.setItem(`a_${new Date().toLocaleDateString()}_${form.dxz_id}`, JSON.stringify(val))
+    props.storage && sessionStorage.setItem(Storagekey, JSON.stringify(val))
   },
   {
     deep: true,
     immediate: true
   })
+
+watch(
+  () => props.d.dxz_id,
+  (val, oldVal) => {
+    if (oldVal !== val) {
+      Storagekey = `a_${new Date().toLocaleDateString()}_${val}`
+      form.dxz_id = val
+    }
+  },
+  {
+    deep: true,
+    // immediate: true
+  })
 </script>
 
 <template>
@@ -50,44 +64,52 @@ watch(
 
     <div class="flex justify-between items-end">
       <div class="p-1 text-base font-bold">咨询记录</div>
+      <!-- #ifdef DEV -->
+      <!-- {{ d.dxz_id }} -->
+      <!-- #endif -->
       <div class="p-1">{{ form.dxxd_date }}</div>
     </div>
     <div class="divide-x divide-dark-100 flex justify-start">
       <div class="flex-none px-2px w-66px leading-7">来访者姓名</div>
-      <div class="px-2px flex-auto leading-7 max-w-56px"><el-input :disabled="disabled"
-          input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_lfzxm" size="small"></el-input>
+      <div class="px-2px flex-auto leading-7 max-w-56px"><el-input autocomplete="off" name="dxxd_lfzxm"
+          :disabled="disabled" input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_lfzxm"
+          size="small"></el-input>
       </div>
       <div class="flex-none px-2px w-30px leading-7">学校</div>
-      <div class="px-2px flex-auto leading-7"><el-input :disabled="disabled"
+      <div class="px-2px flex-auto leading-7"><el-input autocomplete="off" name="dxxd_school_name" :disabled="disabled"
           input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_school_name" size="small"></el-input></div>
       <div class="flex-none px-2px w-30px leading-7">班级</div>
-      <div class="px-2px flex-auto leading-7"><el-input :disabled="disabled"
+      <div class="px-2px flex-auto leading-7"><el-input autocomplete="off" name="dxxd_class_name" :disabled="disabled"
           input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_class_name" size="small"></el-input></div>
     </div>
     <div class="divide-x divide-dark-100 flex justify-start">
       <div class="flex-none px-2px w-66px leading-7">接访教师</div>
-      <div class="px-2px flex-auto leading-7 max-w-56px"><el-input :disabled="disabled"
-          input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_jfls" size="small"></el-input>
+      <div class="px-2px flex-auto leading-7 max-w-56px"><el-input autocomplete="off" name="dxxd_jfls"
+          :disabled="disabled" input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_jfls"
+          size="small"></el-input>
       </div>
       <div class="flex-none px-2px w-30px leading-7">住址</div>
-      <div class="px-2px flex-auto leading-7"><el-input :disabled="disabled"
+      <div class="px-2px flex-auto leading-7"><el-input autocomplete="off" name="dxxd_address" :disabled="disabled"
           input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_address" size="small"></el-input></div>
       <div class="flex-none px-2px w-30px leading-7">电话</div>
-      <div class="px-2px flex-auto leading-7"><el-input :disabled="disabled"
+      <div class="px-2px flex-auto leading-7"><el-input autocomplete="off" name="dxxd_phone" :disabled="disabled"
           input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_phone" size="small"></el-input></div>
     </div>
 
     <div class="p-1">
-      <div>咨询缘由: </div><el-input :disabled="disabled" input-style="font-size:12px;line-height:14px;"
-        v-model="form.dxxd_zxyy" type="textarea" resize="none" :rows="2"></el-input>
+      <div>咨询缘由: </div><el-input autocomplete="off" name="dxxd_zxyy" :disabled="disabled"
+        input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_zxyy" type="textarea" resize="none"
+        :rows="2"></el-input>
     </div>
     <div class="p-1">
-      <div>来访者自述:</div><el-input :disabled="disabled" input-style="font-size:12px;line-height:14px;"
-        v-model="form.dxxd_lfzzs" type="textarea" resize="none" :rows="2"></el-input>
+      <div>来访者自述:</div><el-input autocomplete="off" name="dxxd_lfzzs" :disabled="disabled"
+        input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_lfzzs" type="textarea" resize="none"
+        :rows="2"></el-input>
     </div>
     <div class="p-1">
-      <div>初步分析:</div><el-input :disabled="disabled" input-style="font-size:12px;line-height:14px;"
-        v-model="form.dxxd_cbfx" type="textarea" resize="none" :rows="2"></el-input>
+      <div>初步分析:</div><el-input autocomplete="off" name="dxxd_cbfx" :disabled="disabled"
+        input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_cbfx" type="textarea" resize="none"
+        :rows="2"></el-input>
     </div>
     <div class="p-1">
       <div>危机等级:</div>
@@ -98,8 +120,9 @@ watch(
       </el-radio-group>
     </div>
     <div class="p-1">
-      <div>处理方式:</div><el-input :disabled="disabled" input-style="font-size:12px;line-height:14px;"
-        v-model="form.dxxd_clfs" type="textarea" resize="none" :rows="2"></el-input>
+      <div>处理方式:</div><el-input autocomplete="off" name="dxxd_clfs" :disabled="disabled"
+        input-style="font-size:12px;line-height:14px;" v-model="form.dxxd_clfs" type="textarea" resize="none"
+        :rows="2"></el-input>
     </div>
 
   </div>

+ 0 - 0
src/components/chat-textarea/index.vue


+ 9 - 4
src/components/info-item/index.vue

@@ -1,13 +1,16 @@
 <script setup lang="ts">
 import type { type_dyaw_xlfw_zxhd_log } from '~/types';
-import { formatTimestamp,formatTimeToShow } from '~/utils/time';
+import { formatTimestamp, formatTimeToShow } from '~/utils/time';
 const props = defineProps<{
   left: boolean,
+  virtualName?: string,
+  w: number,
   d: type_dyaw_xlfw_zxhd_log
 }>()
 
+
 const id2name = {
-  [props.d.dxzl_stu_user_id]: props.d.dxzl_stu_user_realname,
+  [props.d.dxzl_stu_user_id]: props.virtualName ?? props.d.dxzl_stu_user_realname,
   [props.d.dxzl_tea_user_id]: props.d.dxzl_tea_user_realname,
 }
 
@@ -19,10 +22,11 @@ const showMsg = $computed(() => decodeURIComponent(props.d.dxzl_last_msg_content
   <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] }}</div>
-      <div class="text-sm text-hex-AFB2B6">{{ formatTimeToShow(formatTimestamp(props.d.create_dateline)) }}</div>
+      <div class="text-sm text-hex-AFB2B6">{{ formatTimeToShow(props.d.create_dateline * 1000) }}</div>
     </div>
     <div class="flex mt-2 r_flex-row-reverse">
-      <div class=" py-10px px-25px rounded-3xl min-h-44px bg-white text_wrapper" v-html="showMsg">
+      <div class=" py-10px px-25px rounded-3xl min-h-44px bg-white text_wrapper" :style="`max-width:${props.w}px`"
+        v-html="showMsg">
       </div>
     </div>
 
@@ -33,6 +37,7 @@ const showMsg = $computed(() => decodeURIComponent(props.d.dxzl_last_msg_content
 .text_wrapper :deep(p) {
   margin: 0;
   padding: 0;
+  word-wrap: break-word;
 }
 
 

+ 30 - 11
src/components/rtc-dialog/index.vue

@@ -8,7 +8,7 @@ import type { type_dyaw_xlfw_zxhd, type_dyaw_xlfw_zxhd_log } from '~/types';
 import user, { UserRole } from '~/store/user';
 import { socketSend } from '~/utils/ws';
 import { formatOffsetSec } from '~/utils/time';
-
+import { ElMessage } from "element-plus";
 let offsetTimer: NodeJS.Timeout
 let offsetTime = $ref<number>(0)
 
@@ -115,6 +115,7 @@ defineExpose({
         offsetTimer && clearInterval(offsetTimer)
         isOpen = false
         await rtcInstance.client!.leave();
+        handleSwitchUse(false)
         break;
       default:
         break;
@@ -126,7 +127,7 @@ function handleInfoAdd(tip: string) {
   const _reqDate = Object.assign({
     dxzl_status: '1',
     dxzl_type: mode === 'audio' ? '3' : '4',
-    dxzl_last_msg_content: encodeURIComponent(`【${mode === 'audio' ? '语音通话' : '视频通话'}】`),
+    dxzl_last_msg_content: encodeURIComponent(`【${mode === 'audio' ? '语音通话' : '视频通话'}】 ${tip}`),
   }, reqDate)
   return request({
     url: '/dyaw/xlfw_zxhd_log/add',
@@ -195,8 +196,22 @@ type TRtcOptions = {
 }
 let rtcOptions: TRtcOptions;
 
+function handleSwitchUse(v: boolean) {
+  try {
+    audioInUse = v
+    videoInUse = v
+    rtcInstance?.localAudioTrack?.setEnabled(v)
+    rtcInstance?.localVideoTrack?.setEnabled(v)
+  } catch (error) {
+    console.error(error);
+  }
+}
+
 function initRtcClient() {
-  if (rtcInstance.client) return;
+  if (rtcInstance.client) {
+    handleSwitchUse(true)
+    return
+  };
   const client = rtcInstance.client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
 
   client.on("user-published", async (user, mediaType) => {
@@ -253,6 +268,7 @@ async function handleAudioChatStart() {
     if (isBusy) {
       // busy operation
       currentChatStatus = CHAT_STATUS.WAITING_BUSY
+      ElMessage.warning('对方忙线中')
       setTimeout(() => {
         handleClose()
       }, 2000)
@@ -306,6 +322,7 @@ async function handleAudioChatCancel() {
   })
   isOpen = false
   rtcInstance.client?.leave();
+  handleSwitchUse(false)
 
 }
 async function handleAudioChatAccept() {
@@ -348,9 +365,11 @@ async function handleAudioChatDeny() {
   isOpen = false
 }
 async function handleAudioChatEnd() {
-  const fullSendData = await handleInfoEdit({ dxzl_status: '4' }, '已结束')
+  const fullSendData = await handleInfoEdit({ dxzl_status: '4' }, /*'已结束'*/formatOffsetSec(offsetTime))
   offsetTimer && clearInterval(offsetTimer)
   await rtcInstance.client!.leave();
+  handleSwitchUse(false)
+
   socketSend(ws2, {
     dxzl_stu_user_id: dyaw_xlfw_zxhd!.dxz_stu_user_id,
     dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
@@ -363,8 +382,8 @@ async function handleAudioChatEnd() {
 let audioInUse = $ref(true)
 let videoInUse = $ref(true)
 
-function handleSwitchAudio() {
-  audioInUse = !audioInUse
+function handleSwitchAudio(v?: boolean) {
+  audioInUse = v ?? !audioInUse
   try {
     rtcInstance.localAudioTrack!.setEnabled(audioInUse)
   } catch (error) {
@@ -372,8 +391,8 @@ function handleSwitchAudio() {
   }
 
 }
-function handleSwitchVideo() {
-  videoInUse = !videoInUse
+function handleSwitchVideo(v?: boolean) {
+  videoInUse = v ?? !videoInUse
   try {
     rtcInstance.localVideoTrack!.setEnabled(videoInUse)
   } catch (error) {
@@ -408,7 +427,7 @@ function handleChangeMins() {
 <template>
   <UseDraggable v-if="isOpen" storage-key="chat-audio" storage-type="session" :initial-value="{ x: 584, y: 207 }"
     class="fixed bg-transparent cursor-move z-4000">
-    <div v-show="mins">
+    <div v-show="mins" class="w-375px h-0 flex justify-end">
       <el-avatar :size="90" :src="otherInfo?.avatar" @mousedown="isDown = true" @mousemove="isMove = isDown"
         @click="handleChangeMins"></el-avatar>
     </div>
@@ -439,7 +458,7 @@ function handleChangeMins() {
             </div>
             <div v-show="currentChatStatus === CHAT_STATUS.CHATING"
               class="bg-hex-efefef text-hex-272636 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
-              @click="handleSwitchAudio">
+              @click="handleSwitchAudio()">
               <i:ant-design:audio-outlined v-show="audioInUse" />
               <i:ant-design:audio-muted-outlined v-show="!audioInUse" />
             </div>
@@ -456,7 +475,7 @@ function handleChangeMins() {
             </div>
             <div v-show="mode === 'video' && currentChatStatus === CHAT_STATUS.CHATING"
               class="bg-hex-efefef text-hex-272636 w-12 h-12 rounded-1 cursor-pointer flex items-center justify-around"
-              @click="handleSwitchVideo">
+              @click="handleSwitchVideo()">
               <i:material-symbols:video-camera-back-rounded v-show="videoInUse" />
               <i:material-symbols:video-camera-front-off-rounded v-show="!videoInUse" />
             </div>

+ 12 - 1
src/components/tinymce-area/index.vue

@@ -18,7 +18,7 @@ const props = defineProps({
   },
 })
 
-const emits = defineEmits(['update:modelValue', 'click:audio', 'click:video'])
+const emits = defineEmits(['update:modelValue', 'click:audio', 'click:video', 'click:submit'])
 
 const content = ref('')
 
@@ -40,7 +40,18 @@ function init() {
     plugins: 'image emoticons',
     autosave_restore_when_empty: false,
     content_style: 'body { margin: 0.4rem; line-height: 1; } p { margin: 0; } ',
+    setup(_editor) {
+      _editor.on('KeyDown', (e) => {
+        if (e.ctrlKey && e.keyCode === 13) {
+          e.preventDefault();
+          emits('click:submit')
+          return
+        }
+      })
+    },
     init_instance_callback: (_editor) => {
+      // console.log('init_instance_callback :>> ', _editor.isNas);
+
       _editor.on('KeyUp', (e) => {
         submitNewContent()
       })

+ 28 - 11
src/pages/student/consult.vue

@@ -43,6 +43,8 @@ const dyaw_xlfw_zxhd: type_dyaw_xlfw_zxhd = (await request({
   }
 })).data.one_info
 
+
+
 let rateDialogVisible = $ref(false)
 let endTime = $ref<string>()
 let rateNum = $ref(5)
@@ -137,6 +139,15 @@ const ws = createSocket(
     }
   }
 )
+// 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() {
@@ -199,27 +210,34 @@ function emitUpdateInfo(info: type_dyaw_xlfw_zxhd_log, isUpdate?: boolean) {
       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>
+        <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"></info-item>
+        <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> -->
+            <el-input type="textarea"></el-input> -->
         <tinymce-area v-model="inputValue" ref="TinyRef" @click:audio="handleAudioChatStart"
-          @click:video="handleVideoChatStart"></tinymce-area>
+          @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>
@@ -265,14 +283,13 @@ function emitUpdateInfo(info: type_dyaw_xlfw_zxhd_log, isUpdate?: boolean) {
   <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: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> -->
+        <i:material-symbols:video-camera-back-rounded />
+        <i:material-symbols:video-camera-front-off-rounded />
+      </chat-dialog> -->
 </template>
 
 <style scoped lang="scss">
-
 </style>

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

@@ -21,7 +21,7 @@ const teacherList = (await request({
     </div>
     <div
       class="w-400px h-full overflow-y-auto flex flex-col items-center space-y-4 py-4 scrollbar scrollbar-thin scrollbar-thumb-rounded-md scrollbar-thumb-gray-200 scrollbar-track-transparent">
-      <el-empty v-if="!teacherList?.length" description="今天暂无排老师"></el-empty>
+      <el-empty v-if="!teacherList?.length" description="今天暂无排老师"></el-empty>
       <teacher-card v-for="item in teacherList" class="flex-none" :d="item"></teacher-card>
     </div>
   </div>

+ 52 - 15
src/pages/teacher/consult.vue

@@ -7,7 +7,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import user from '~/store/user';
 import { createSocket, socketSend } from '~/utils/ws';
 import type { TSocketRes } from '~/utils/ws';
-import { formatTimestamp } from '~/utils/time';
+import { formatTimestamp2Date } from '~/utils/time';
 import { CHAT_STATUS, CHAT_OPERATION } from '~/types';
 let dyaw_xlfw_zxhd = $ref<type_dyaw_xlfw_zxhd | undefined>()
 let dyaw_xlfw_zxhd_list = $ref<type_dyaw_xlfw_zxhd[] | undefined>()
@@ -23,9 +23,9 @@ let timer: NodeJS.Timeout;
       last_msg: 1
     }
   })).data.page_data
-  if(dyaw_xlfw_zxhd!==undefined){
+  if (dyaw_xlfw_zxhd !== undefined) {
     const temp = dyaw_xlfw_zxhd_list!.find(item => item.dxz_stu_user_id === dyaw_xlfw_zxhd!.dxz_stu_user_id)
-    if(temp?.dxz_id!==dyaw_xlfw_zxhd?.dxz_id){
+    if (temp?.dxz_id !== dyaw_xlfw_zxhd?.dxz_id) {
       dyaw_xlfw_zxhd = temp
     }
   }
@@ -100,9 +100,33 @@ async function handleClickSend() {
 let ws = $ref<WebSocket>()
 
 let cardLoading = $ref(false)
+let unreadNum = $ref(0)
+
+// function getAllUnreadMsg() {
+//   request({
+//     url: '/dyaw/xlfw_zxhd_log/index',
+//     data: {
+//       dxzl_stu_user_id: dyaw_xlfw_zxhd?.dxz_stu_user_id,
+//       dxzl_tea_user_id: dyaw_xlfw_zxhd!.dxz_tea_user_id,
+//       limit: 1
+//     }
+//   }).then(res => {
+//     if (res.code === '1') {
+//       ifScroll = false
+//       infoList = (res.data.page_data.reverse())
+//       unreadNum = 0
+//     }
+//     nextTick(() => {
+//       ifScroll = true
+//       // scrollbarRef!.scrollTo(0, 0);
+//     })
+//   })
+// }
+
 function handleClickStuCard(stu: type_dyaw_xlfw_zxhd) {
   cardLoading = true
   dyaw_xlfw_zxhd = stu
+  unreadNum = parseInt(stu.dxz_unread_msg_num)
   stu.dxz_unread_msg_num = "0"
   archivesList = []
 
@@ -120,8 +144,14 @@ function handleClickStuCard(stu: type_dyaw_xlfw_zxhd) {
       ws = createSocket(
         { teacher: user.user_id, student: stu.dxz_stu_user_id },
         {
-          message(socketRes: TSocketRes<type_dyaw_xlfw_zxhd_log>) {
+          message(socketRes: TSocketRes<type_dyaw_xlfw_zxhd_log & { $?: boolean, dxz_stu_user_id?: string, dyaw_xlfw_zxhd: type_dyaw_xlfw_zxhd }>) {
             if (socketRes.from_client_name.endsWith('student')) {
+              if (socketRes.content.$) {
+                if (socketRes.content?.dxz_stu_user_id === dyaw_xlfw_zxhd?.dxz_stu_user_id) {
+                  dyaw_xlfw_zxhd = socketRes.content.dyaw_xlfw_zxhd
+                }
+                return;
+              }
               infoList.push(socketRes.content)
               request({
                 url: '/dyaw/xlfw_zxhd_log/index',
@@ -271,30 +301,39 @@ function emitUpdateInfo(info: type_dyaw_xlfw_zxhd_log, isUpdate?: boolean) {
 <template>
   <div class="h-590px bg-hex-f2f2f295 flex justify-center divide-x">
     <div class="w-320px p-10px h-full flex flex-col justify-start bg-white space-y-4">
-      <el-input v-model="searchValue" size="large" :prefix-icon="Search" clearable></el-input>
+      <el-input autocomplete="off" name="searchValue" v-model="searchValue" size="large" :prefix-icon="Search"
+        clearable></el-input>
       <div
         class="max-h-520px flex flex-col flex-none divide-y scrollbar scrollbar-thin scrollbar-thumb-rounded-md scrollbar-thumb-gray-200 scrollbar-track-transparent">
         <chat-stu-card v-for="item in dyaw_xlfw_zxhd_list" :d="item" @click="handleClickStuCard(item)"
           class="h-68px"></chat-stu-card>
       </div>
     </div>
-    <div class="w-680px h-full divide-y flex flex-col">
+    <div class="w-680px h-full divide-y flex flex-col relative">
       <template v-if="dyaw_xlfw_zxhd">
         <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_stu_user_realname }}
             {{ dyaw_xlfw_zxhd.dxz_stu_school_name }} {{ dyaw_xlfw_zxhd.dxz_class_name }}</span>
         </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">
+          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 relative">
           <div v-show="!cardLoading" @click="handleLoadMoreInfo"
             class="w-full text-center text-sm text-blue-400 hover:underline underline-blue-400 cursor-pointer">查看更多
           </div>
-          <info-item v-for="item in infoList" :key="item.dxzl_id" :left="item.create_user_id === user.user_id"
-            :d="item"></info-item>
+          <info-item v-for="item in infoList" :key="item.dxzl_id" :left="item.create_user_id !== user.user_id" :d="item"
+            :w="500"></info-item>
+
         </div>
+        <!--  -->
+        <!-- <div v-show="unreadNum > 10"
+                        class="absolute bg-white right-2 bottom-200px text-blue-400 rounded-l-full cursor-pointer z-100 flex items-center space-x-1 p-2 text-sm"
+                        @click="getAllUnreadMsg">
+                        <i:material-symbols:keyboard-double-arrow-up />
+                        <span>{{ unreadNum }}条消息</span>
+                      </div> -->
         <div class="bg-white h-180px p-5px flex flex-col justify-between">
           <tinymce-area v-model="inputValue" ref="TinyRef" @click:audio="handleAudioChatStart"
-            @click:video="handleVideoChatStart"></tinymce-area>
+            @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>
@@ -315,15 +354,15 @@ function emitUpdateInfo(info: type_dyaw_xlfw_zxhd_log, isUpdate?: boolean) {
         <div class="w-full"
           v-if="archivesList && (archivesList.length === 0 || dyaw_xlfw_zxhd.dxz_id !== archivesList[0].dxz_id)">
           <archives-card storage ref="ArchivesCardRef"
-            :d="{ dxz_id: dyaw_xlfw_zxhd.dxz_id, user_id: dyaw_xlfw_zxhd.dxz_stu_user_id, dxxd_lfzxm: dyaw_xlfw_zxhd.dxz_stu_user_realname, dxxd_jfls: dyaw_xlfw_zxhd.dxz_tea_user_realname, dxxd_school_name: dyaw_xlfw_zxhd.dxz_stu_school_name, dxxd_class_name: dyaw_xlfw_zxhd.dxz_class_name, dxxd_date: formatTimestamp(dyaw_xlfw_zxhd.create_dateline) }"></archives-card>
+            :d="{ dxz_id: dyaw_xlfw_zxhd.dxz_id, user_id: dyaw_xlfw_zxhd.dxz_stu_user_id, dxxd_lfzxm: dyaw_xlfw_zxhd.dxz_stu_user_realname, dxxd_jfls: dyaw_xlfw_zxhd.dxz_tea_user_realname, dxxd_school_name: dyaw_xlfw_zxhd.dxz_stu_school_name, dxxd_class_name: dyaw_xlfw_zxhd.dxz_class_name, dxxd_date: formatTimestamp2Date(dyaw_xlfw_zxhd.create_dateline) }"></archives-card>
           <div class="flex_center py-2">
             <el-button @click="handleSubmitArchives" type="primary" size="small">提交</el-button>
           </div>
         </div>
-        <div class="bg-hex-FFF7FA py-2">
+        <div class="bg-hex-FFF7FA py-2 space-y-1">
           <div class="flex_center text-lg">历史档案</div>
           <el-empty v-show="archivesList.length === 0" :image-size="60" description="暂无历史档案"></el-empty>
-          <archives-card disabled v-for="item in archivesList" :d="item" :key="item.dxz_id"></archives-card>
+          <archives-card disabled v-for="item in archivesList" :d="item" :key="item.dxxd_id"></archives-card>
         </div>
       </div>
 
@@ -333,9 +372,7 @@ function emitUpdateInfo(info: type_dyaw_xlfw_zxhd_log, isUpdate?: boolean) {
 
 
   <rtc-dialog ref="RtcDialogRef" @update-info="emitUpdateInfo"></rtc-dialog>
-
 </template>
 
 <style scoped lang="scss">
-
 </style>

+ 2 - 0
src/types.ts

@@ -16,6 +16,7 @@ export type type_dyaw_xlfw_zxhd = {
   dxz_id: string;
   create_user_id: string;
   create_dateline: string;
+  old_data?: number;
 }
 
 
@@ -34,6 +35,7 @@ export type type_dyaw_xlfw_zxhd_log = {
 }
 
 export type type_archives_item = {
+  dxxd_id: string;
   dxz_id: string,
   user_id: string;
   dxxd_date?: string;

+ 12 - 5
src/utils/time.ts

@@ -6,14 +6,21 @@ export function formatTimestamp(t: string) {
   return getDatabaseTime(1000 * parseInt(t))
 }
 
+// export function formatDate2String(d: Date) {
+//   return d.toLocaleString().replace(/\//g, '-')
+// }
+
+// format timestamp to yyyy-mm-dd
+export function formatTimestamp2Date(t: string) {
+  return formatDate2String(new Date(1000 * parseInt(t)))
+}
+// format Date to yyyy-mm-dd 不足2位补0
 export function formatDate2String(d: Date) {
-  return d.toLocaleString().replace(/\//g, '-')
+  return `${d.getFullYear()}-${d.getMonth() + 1 < 10 ? '0' + (d.getMonth() + 1) : d.getMonth() + 1}-${d.getDate() < 10 ? '0' + d.getDate() : d.getDate()}`
 }
 
-
-
 // 将一个秒数转换为 00:00:00 格式
-export function formatOffsetSec(s: number):string {
+export function formatOffsetSec(s: number): string {
   const h = Math.floor(s / 3600)
   const m = Math.floor(s % 3600 / 60)
   const ss = Math.floor(s % 3600 % 60)
@@ -21,7 +28,7 @@ export function formatOffsetSec(s: number):string {
 }
 
 // 将LocaleTimeString 改为当天只显示 hh:mm:ss,之前的日期改为yyyy-mm-dd hh:mm:ss
-export function formatTimeToShow(t: string) {
+export function formatTimeToShow(t: string | number) {
   const d = new Date(t)
   const now = new Date()
   if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate()) {

+ 21 - 7
src/utils/ws.ts

@@ -67,11 +67,25 @@ export function createSocket(
 }
 
 export function socketSend(ws: WebSocket, content: unknown) {
-  ws.send(
-    JSON.stringify({
-      type: "say",
-      to_client_id: 'all',
-      content: encodeURIComponent(JSON.stringify(content))
-    })
-  )
+  if (ws.readyState === WebSocket.OPEN) {
+    // WebSocket 连接已建立
+    ws.send(
+      JSON.stringify({
+        type: "say",
+        to_client_id: 'all',
+        content: encodeURIComponent(JSON.stringify(content))
+      })
+    )
+  } else {
+    ws.onopen = () => {
+      ws.send(
+        JSON.stringify({
+          type: "say",
+          to_client_id: 'all',
+          content: encodeURIComponent(JSON.stringify(content))
+        })
+      )
+    }
+  }
+
 }