更新
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="`与 ${patientName} 聊天`"
|
||||
width="1200px"
|
||||
top="5vh"
|
||||
width="900px"
|
||||
top="10vh"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
:destroy-on-close="true"
|
||||
@@ -18,34 +18,62 @@
|
||||
<el-alert :title="error" type="error" :closable="false" />
|
||||
</div>
|
||||
<div v-else class="chat-content">
|
||||
<UIKitProvider>
|
||||
<UIKitProvider language="zh-CN" theme="light">
|
||||
<div class="chat-layout">
|
||||
<ConversationList class="conversation-list" />
|
||||
<Chat class="chat-area">
|
||||
<MessageList />
|
||||
<MessageInput />
|
||||
<MessageList :conversationID="conversationId"/>
|
||||
<MessageInput>
|
||||
<template #headerToolbar>
|
||||
<div class="message-toolbar">
|
||||
<EmojiPicker />
|
||||
<ImagePicker />
|
||||
<FilePicker />
|
||||
<AudioCallPicker />
|
||||
<VideoCallPicker />
|
||||
</div>
|
||||
</template>
|
||||
</MessageInput>
|
||||
</Chat>
|
||||
</div>
|
||||
<!-- TUICallKit 放在 dialog 外部,独立渲染 -->
|
||||
|
||||
</UIKitProvider>
|
||||
</div>
|
||||
</div>
|
||||
<TUICallKit
|
||||
|
||||
:allowedMinimized="true"
|
||||
:allowedFullScreen="true"
|
||||
class="chat-dialog-call-kit"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, nextTick, watch } from 'vue'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import { getCallSignature } from '@/api/tcm'
|
||||
import feedback from '@/utils/feedback'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import {
|
||||
ConversationList,
|
||||
Chat,
|
||||
MessageList,
|
||||
MessageInput,
|
||||
UIKitProvider,
|
||||
useLoginState
|
||||
useLoginState,
|
||||
useConversationListState,
|
||||
EmojiPicker,
|
||||
ImagePicker,
|
||||
FilePicker,
|
||||
AudioCallPicker,
|
||||
VideoCallPicker
|
||||
} from '@tencentcloud/chat-uikit-vue3'
|
||||
import TencentCloudChat from '@tencentcloud/chat'
|
||||
import '@/assets/chat-uikit.css'
|
||||
import { TUICallKit } from '@tencentcloud/call-uikit-vue'
|
||||
|
||||
const visible = ref(false)
|
||||
const isReady = ref(false)
|
||||
@@ -55,8 +83,130 @@ const patientId = ref<number | null>(null)
|
||||
const diagnosisId = ref<number | null>(null)
|
||||
const loadingText = ref('正在初始化...')
|
||||
const patientUserId = ref('')
|
||||
|
||||
const { login, logout } = useLoginState()
|
||||
const { setActiveConversation, createC2CConversation, activeConversation } = useConversationListState()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 更新用户头像到 IM
|
||||
const updateUserAvatarToIM = async (avatarUrl: string,appid: number) => {
|
||||
try {
|
||||
if (!avatarUrl) {
|
||||
console.warn('头像 URL 为空,跳过更新')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('开始更新头像到 IM:', avatarUrl)
|
||||
|
||||
// 尝试通过 SDK 实例更新头像
|
||||
// 在 UIKit 登录后,SDK 实例应该已初始化
|
||||
const updateResult = await new Promise((resolve, reject) => {
|
||||
// 延迟执行,确保 SDK 完全初始化
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// 尝试多种方式获取 SDK 实例
|
||||
|
||||
let chat = TencentCloudChat.create( { SDKAppID: appid });
|
||||
// 调用 updateMyProfile 更新用户头像
|
||||
const response = await chat.updateMyProfile({
|
||||
avatar: avatarUrl
|
||||
})
|
||||
|
||||
console.log('IM 头像更新成功:', response)
|
||||
resolve(response)
|
||||
} catch (err) {
|
||||
console.error('更新头像异常:', err)
|
||||
resolve(null)
|
||||
}
|
||||
}, 500)
|
||||
})
|
||||
|
||||
} catch (err: any) {
|
||||
console.error('IM 头像更新失败:', err.message || err)
|
||||
// 不中断主流程,头像更新失败不影响聊天功能
|
||||
}
|
||||
}
|
||||
|
||||
// 监听当前激活的会话变化,更新标题
|
||||
watch(() => activeConversation.value, (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"]')
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
const open = async (data: { patientId: number; patientName: string; diagnosisId?: number }) => {
|
||||
visible.value = true
|
||||
@@ -82,24 +232,52 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
|
||||
|
||||
patientUserId.value = res.patientUserId || `patient_${data.patientId}`
|
||||
|
||||
loadingText.value = '正在登录聊天系统...'
|
||||
loadingText.value = '正在登录聊天...'
|
||||
|
||||
// 登录 IM
|
||||
// 登录 Chat UIKit
|
||||
await login({
|
||||
sdkAppId: Number(res.sdkAppId),
|
||||
userId: res.userId,
|
||||
userSig: res.userSig,
|
||||
useUploadPlugin: true
|
||||
})
|
||||
const sdkAPPid=Number(res.sdkAppId);
|
||||
|
||||
console.log('IM 登录成功')
|
||||
console.log('Chat UIKit 登录成功,患者用户ID:', patientUserId.value)
|
||||
|
||||
loadingText.value = '加载聊天界面...'
|
||||
|
||||
// 等待一下让组件完全初始化
|
||||
setTimeout(() => {
|
||||
// 等待 UIKit 初始化完成后创建并激活会话
|
||||
setTimeout(async () => {
|
||||
isReady.value = true
|
||||
}, 500)
|
||||
|
||||
// 使用 nextTick 确保组件已渲染
|
||||
await nextTick()
|
||||
|
||||
// 增加延迟,确保 TIM SDK 完全初始化
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 更新用户头像到 IM - 从 Pinia store 获取当前登录用户的头像
|
||||
const avatar = userStore.userInfo?.avatar
|
||||
console.log('准备更新头像,avatar:', avatar)
|
||||
|
||||
if (avatar) {
|
||||
await updateUserAvatarToIM(avatar,sdkAPPid)
|
||||
}
|
||||
|
||||
// 创建或获取与患者的单聊会话
|
||||
try {
|
||||
const conversation = await createC2CConversation(patientUserId.value)
|
||||
conversationId.value = conversation.conversationID
|
||||
console.log('已创建/获取会话:', conversationId.value)
|
||||
|
||||
// 激活该会话
|
||||
setActiveConversation(conversationId.value)
|
||||
console.log('已激活会话')
|
||||
} catch (err) {
|
||||
console.error('创建/激活会话失败:', err)
|
||||
}
|
||||
}, 800)
|
||||
|
||||
} catch (err: any) {
|
||||
console.error('初始化聊天失败:', err)
|
||||
@@ -108,15 +286,21 @@ const open = async (data: { patientId: number; patientName: string; diagnosisId?
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
// 登出
|
||||
logout()
|
||||
const handleClose = async () => {
|
||||
try {
|
||||
// 登出 Chat
|
||||
// await logout()
|
||||
} catch (err) {
|
||||
console.error('登出失败:', err)
|
||||
}
|
||||
|
||||
visible.value = false
|
||||
isReady.value = false
|
||||
error.value = ''
|
||||
patientId.value = null
|
||||
diagnosisId.value = null
|
||||
patientUserId.value = ''
|
||||
// isReady.value = false
|
||||
// error.value = ''
|
||||
// patientId.value = null
|
||||
// diagnosisId.value = null
|
||||
// patientUserId.value = ''
|
||||
// conversationId.value = ''
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
@@ -127,12 +311,23 @@ defineExpose({ open })
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 0;
|
||||
height: 75vh;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
// 确保对话框有合适的 z-index
|
||||
:deep(.el-overlay) {
|
||||
z-index: 2500;
|
||||
}
|
||||
|
||||
// 确保对话框本身的 z-index 低于 TUICallKit
|
||||
:deep(.el-dialog) {
|
||||
z-index: 2501;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
@@ -163,6 +358,7 @@ defineExpose({ open })
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-layout {
|
||||
@@ -179,7 +375,85 @@ defineExpose({ open })
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.empty-chat {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 消息工具栏样式 */
|
||||
.message-toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 全局样式:确保表情选择器等弹出层有足够高的 z-index */
|
||||
.el-popper,
|
||||
.el-picker-panel,
|
||||
[data-reka-popper],
|
||||
[data-reka-popper-content-wrapper],
|
||||
[class*="emoji-picker"],
|
||||
[class*="picker-popup"],
|
||||
[class*="tui-picker"],
|
||||
body > div[id^="el-popper-container"],
|
||||
body > div[data-reka-focus-guard] {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
/* 确保聊天对话框内的弹出层也能正确显示 */
|
||||
.chat-dialog [data-reka-popper-content-wrapper] {
|
||||
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>
|
||||
|
||||
@@ -998,15 +998,7 @@ defineExpose({
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 隐藏 TUICallKit 自带的全屏容器 */
|
||||
:global(.TUICallKit-desktop),
|
||||
:global(.TUICallKit-mobile),
|
||||
:global(body > .TUICallKit-desktop),
|
||||
:global(body > .TUICallKit-mobile) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 只显示我们窗口内的 TUICallKit */
|
||||
/* 只在我们的浮动窗口内显示 TUICallKit */
|
||||
.floating-video-window .TUICallKit-desktop,
|
||||
.floating-video-window .TUICallKit-mobile {
|
||||
display: block !important;
|
||||
@@ -1016,14 +1008,8 @@ defineExpose({
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* 确保 TUICallKit 不会创建遮罩层 */
|
||||
/* 确保 Element Plus 的遮罩层不会覆盖视频通话 */
|
||||
:global(.el-overlay) {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
/* 隐藏所有可能的 TUICallKit 弹出层 */
|
||||
:global(body > div[class*="TUICall"]),
|
||||
:global(body > div[id*="tui-call"]) {
|
||||
display: none !important;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user