This commit is contained in:
Your Name
2026-03-13 14:08:52 +08:00
parent 08dd9cd307
commit eb283f008b
667 changed files with 8425 additions and 27053 deletions
+127 -134
View File
@@ -22,31 +22,40 @@
<div class="chat-layout">
<ConversationList class="conversation-list" />
<Chat class="chat-area">
<MessageList :conversationID="conversationId"/>
<MessageList :conversationID="conversationId" />
<MessageInput>
<template #headerToolbar>
<div class="message-toolbar">
<EmojiPicker />
<ImagePicker />
<FilePicker />
<AudioCallPicker />
<VideoCallPicker />
<!-- 仅在 TUICallKit 初始化成功后展示音视频入口避免初始化登录未完成错误 -->
<AudioCallPicker v-if="isCallReady" />
<VideoCallPicker v-if="isCallReady" />
<!-- 群组视频通话基于 TUICallKitServer.calls多人通话入口 -->
<el-button
v-if="isCallReady"
size="small"
type="primary"
@click="startGroupVideoCall"
>
群视频
</el-button>
</div>
</template>
</MessageInput>
</Chat>
</div>
<!-- TUICallKit 放在 dialog 外部独立渲染 -->
</UIKitProvider>
</div>
</div>
<TUICallKit
:allowedMinimized="true"
:allowedFullScreen="true"
class="chat-dialog-call-kit"
/>
<!-- 挂载 TUICallKit 组件固定居中显示 -->
<TUICallKit
v-if="isCallReady"
:allowedMinimized="true"
:allowedFullScreen="true"
class="chat-dialog-call-kit"
/>
</el-dialog>
</template>
@@ -73,8 +82,8 @@ import {
} from '@tencentcloud/chat-uikit-vue3'
import TencentCloudChat from '@tencentcloud/chat'
import '@/assets/chat-uikit.css'
import { TUICallKit } from '@tencentcloud/call-uikit-vue'
import { TUICallKit, TUICallKitServer, TUICallType } from '@tencentcloud/call-uikit-vue'
const assistant_ids=ref('');
const visible = ref(false)
const isReady = ref(false)
const error = ref('')
@@ -83,6 +92,8 @@ const patientId = ref<number | null>(null)
const diagnosisId = ref<number | null>(null)
const loadingText = ref('正在初始化...')
const patientUserId = ref('')
const conversationId = ref('')
const isCallReady = ref(false)
const { login, logout } = useLoginState()
const { setActiveConversation, createC2CConversation, activeConversation } = useConversationListState()
const userStore = useUserStore()
@@ -127,84 +138,29 @@ const updateUserAvatarToIM = async (avatarUrl: string,appid: number) => {
}
// 监听当前激活的会话变化,更新标题
watch(() => activeConversation.value, (newConversation) => {
watch(() => activeConversation.value, async (newConversation) => {
if (newConversation) {
// 更新标题为当前会话的显示名称
patientName.value = newConversation.getShowName() || patientName.value
console.log('会话切换:', newConversation.conversationID, '名称:', patientName.value)
}
})
// 限制 TUICallKit 拖动范围 - 使用事件监听而不是 MutationObserver
const setupCallKitDragLimit = () => {
setTimeout(() => {
const callKitElements = document.querySelectorAll('[class*="TUICallKit"], [id*="tui-call"]')
console.log('会话切换:', newConversation.conversationID, '名称:', patientName.value, newConversation)
callKitElements.forEach((element: any) => {
if (!element) return
// 监听鼠标释放事件(拖动结束)
element.addEventListener('mouseup', () => {
setTimeout(() => {
const rect = element.getBoundingClientRect()
const dialogElement = document.querySelector('.chat-dialog .el-dialog')
if (!dialogElement) return
const dialogRect = dialogElement.getBoundingClientRect()
// 获取当前位置
const currentLeft = parseInt(element.style.left || '0')
const currentTop = parseInt(element.style.top || '0')
let newLeft = currentLeft
let newTop = currentTop
let needAdjust = false
// 限制左边界
if (rect.left < dialogRect.left) {
newLeft = currentLeft + (dialogRect.left - rect.left)
needAdjust = true
}
// 限制右边界
if (rect.right > dialogRect.right) {
newLeft = currentLeft - (rect.right - dialogRect.right)
needAdjust = true
}
// 限制上边界
if (rect.top < dialogRect.top) {
newTop = currentTop + (dialogRect.top - rect.top)
needAdjust = true
}
// 限制下边界
if (rect.bottom > dialogRect.bottom) {
newTop = currentTop - (rect.bottom - dialogRect.bottom)
needAdjust = true
}
// 应用新位置(使用过渡动画)
if (needAdjust) {
element.style.transition = 'left 0.3s ease, top 0.3s ease'
element.style.left = `${newLeft}px`
element.style.top = `${newTop}px`
setTimeout(() => {
element.style.transition = ''
}, 300)
}
}, 10)
})
})
}, 1000)
}
// 监听 isReady 变化,当通话组件加载后设置拖动限制
watch(() => isReady.value, (ready) => {
if (ready) {
setupCallKitDragLimit()
if (newConversation.userProfile.userID.includes("doctor")) {
console.log('当前会话是医生,跳过获取 assistant_id')
} else {
try {
const res = await getCallSignature({
patient_id: newConversation.userProfile.userID.replace("patient_", ""),
diagnosis_id: diagnosisId.value
})
patientUserId.value = res.patientUserId
assistant_ids.value = res.assistant_id
console.log('会话切换后获取到 assistant_id:', assistant_ids.value, '完整响应:', res)
} catch (err) {
console.error('获取 assistant_id 失败:', err)
}
}
console.log('会话切换完成,userID:', newConversation.userProfile.userID)
}
})
@@ -215,21 +171,27 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
diagnosisId.value = data.diagnosisId || null
error.value = ''
isReady.value = false
isCallReady.value = false
loadingText.value = '正在获取签名...'
try {
// 获取医生的签名信息
const res = await getCallSignature({
patient_id: data.patientId,
diagnosis_id: data.diagnosisId || 0
})
assistant_ids.value = res.assistant_id
console.log('后端返回的签名数据:', res)
console.log('assistant_id:', assistant_ids.value)
if (!res || !res.userSig) {
throw new Error('获取签名失败')
}
if (!res.assistant_id) {
console.warn('警告:后端未返回 assistant_id,群视频通话可能无法正常工作')
}
patientUserId.value = res.patientUserId || `patient_${data.patientId}`
loadingText.value = '正在登录聊天...'
@@ -241,8 +203,23 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
userSig: res.userSig,
useUploadPlugin: true
})
const sdkAPPid=Number(res.sdkAppId);
const sdkAPPid = Number(res.sdkAppId)
// 初始化 TUICallKit(聊天内音视频通话依赖此初始化);只需全局成功一次
if (!(window as any).__TUICallKitInited__) {
loadingText.value = '正在初始化通话能力...'
try {
await TUICallKitServer.init({
userID: res.userId,
userSig: res.userSig,
SDKAppID: sdkAPPid
})
;(window as any).__TUICallKitInited__ = true
} catch (err: any) {
console.warn('TUICallKit 初始化失败,聊天仍可用,音视频通话不可用:', err?.message || err)
}
}
isCallReady.value = !!(window as any).__TUICallKitInited__
console.log('Chat UIKit 登录成功,患者用户ID:', patientUserId.value)
loadingText.value = '加载聊天界面...'
@@ -286,6 +263,51 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
}
}
// 群组视频通话(多人通话):当前医生作为发起方,默认先邀请当前会话患者
const startGroupVideoCall = async () => {
if (!isCallReady.value) {
feedback.msgWarning('通话服务初始化中,请稍后再试')
return
}
if (!patientUserId.value) {
feedback.msgWarning('未找到患者通话ID,无法发起群视频')
return
}
try {
// 确保 assistant_id 存在
if (!assistant_ids.value) {
console.warn('assistant_id 为空,尝试重新获取')
const res = await getCallSignature({
patient_id: patientId.value!,
diagnosis_id: diagnosisId.value
})
assistant_ids.value = res.assistant_id
console.log('重新获取 assistant_id:', assistant_ids.value)
}
// 构建用户列表,过滤掉空值
const userIDList = [patientUserId.value, assistant_ids.value].filter(id => id)
if (userIDList.length < 2) {
feedback.msgWarning('助理ID获取失败,无法发起群视频')
console.error('userIDList 不完整:', userIDList, 'assistant_ids:', assistant_ids.value)
return
}
console.log('发起群视频通话,参与者:', userIDList)
await TUICallKitServer.calls({
userIDList,
type: TUICallType.VIDEO_CALL
})
} catch (err: any) {
console.error('发起群视频失败:', err)
feedback.msgError(err?.message || '发起群视频失败')
}
}
const handleClose = async () => {
try {
// 登出 Chat
@@ -392,6 +414,20 @@ defineExpose({ open })
gap: 8px;
align-items: center;
}
/* TUICallKit 容器样式 - 限制在对话框内拖动 */
.chat-dialog-call-kit {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
width: 600px !important;
height: 400px !important;
max-width: 90% !important;
max-height: 80% !important;
z-index: 300000 !important;
pointer-events: auto !important;
}
</style>
<style scoped lang="scss">
@@ -413,47 +449,4 @@ body > div[data-reka-focus-guard] {
z-index: 9999 !important;
}
/* TUICallKit 容器样式 - 限制在对话框内拖动 */
.chat-dialog-call-kit {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
width: 600px !important;
height: 400px !important;
max-width: 90% !important;
max-height: 80% !important;
z-index: 300000 !important;
pointer-events: auto !important;
}
.float-window-container{
z-index: 3000;
}
/* 限制 TUICallKit 的拖动范围在对话框内 */
.chat-content {
position: relative !important;
}
/* 确保 TUICallKit 相关的所有容器都在对话框之上 */
body > div[class*="TUICallKit"],
body > div[id*="tui-call"],
body > div[class*="t-call"],
body > .TUICallKit-desktop,
body > .TUICallKit-mobile {
position: fixed !important;
z-index: 300000 !important;
max-width: 90vw !important;
max-height: 90vh !important;
pointer-events: auto !important;
}
/* 确保最小化后的通话窗口也在最上层 */
[class*="TUICallKit"][class*="minimized"],
[class*="TUICallKit"][class*="minimize"],
[class*="call-kit-mini"],
[class*="t-call-mini"] {
z-index: 300000 !important;
pointer-events: auto !important;
}
</style>
@@ -564,6 +564,15 @@ const startCall = async () => {
statusText.value = '等待初始化完成...'
await new Promise(resolve => setTimeout(resolve, 2000))
// 生产包中 init 返回后引擎可能尚未挂载,短暂轮询等待 .calls 可用
for (let i = 0; i < 6; i++) {
if (typeof TUICallKitAPI.calls === 'function') break
await new Promise(resolve => setTimeout(resolve, 500))
}
if (typeof TUICallKitAPI.calls !== 'function') {
throw new Error('通话引擎未就绪,请关闭窗口后重试或刷新页面')
}
console.log('初始化完成,显示通话界面')
statusText.value = '准备发起通话...'