832 lines
25 KiB
Vue
832 lines
25 KiB
Vue
<template>
|
|
<div
|
|
:class="{
|
|
'tui-chat': true,
|
|
'tui-chat-h5': isMobile
|
|
}"
|
|
@click="onMessageListBackgroundClick"
|
|
>
|
|
<!-- <JoinGroupCard /> -->
|
|
<div class="tui-chat-main">
|
|
<div v-if="isOfficial" class="tui-chat-safe-tips">
|
|
<span>
|
|
{{
|
|
TUITranslateService.t(
|
|
'TUIChat.【安全提示】本 APP 仅用于体验腾讯云即时通信 IM 产品功能,不可用于业务洽谈与拓展。请勿轻信汇款、中奖等涉及钱款的信息,勿轻易拨打陌生电话,谨防上当受骗。'
|
|
)
|
|
}}
|
|
</span>
|
|
<a @click="openComplaintLink(Link.complaint)">
|
|
{{ TUITranslateService.t('TUIChat.点此投诉') }}
|
|
</a>
|
|
</div>
|
|
<MessageGroupApplication
|
|
v-if="isGroup"
|
|
:key="props.groupID"
|
|
:groupID="props.groupID"
|
|
/>
|
|
<scroll-view
|
|
id="messageScrollList"
|
|
class="tui-message-list"
|
|
scroll-y="true"
|
|
:scroll-top="scrollTop"
|
|
:scroll-into-view="`tui-${historyFirstMessageID}`"
|
|
@scroll="handelScrollListScroll"
|
|
>
|
|
<p
|
|
v-if="!isCompleted"
|
|
class="message-more"
|
|
@click="getHistoryMessageList"
|
|
>
|
|
{{ TUITranslateService.t('TUIChat.查看更多') }}
|
|
</p>
|
|
<li
|
|
v-for="(item, index) in messageList"
|
|
:id="`tui-${item.ID}`"
|
|
:key="item.vueForRenderKey"
|
|
:class="'message-li ' + item.flow"
|
|
>
|
|
<MessageTimestamp
|
|
:currTime="item.time"
|
|
:prevTime="index > 0 ? messageList[index - 1].time : 0"
|
|
/>
|
|
<div class="message-item" @click="toggleID = ''">
|
|
<MessageTip
|
|
v-if="
|
|
item.type === TYPES.MSG_GRP_TIP ||
|
|
isCreateGroupCustomMessage(item)
|
|
"
|
|
:content="item.getMessageContent()"
|
|
/>
|
|
<div
|
|
v-else-if="!item.isRevoked && !isPluginMessage(item)"
|
|
:id="`msg-bubble-${item.ID}`"
|
|
class="message-bubble-container"
|
|
@longpress="
|
|
handleToggleMessageItem($event, item, index, true)
|
|
"
|
|
@touchstart="
|
|
handleH5LongPress($event, item, index, 'touchstart')
|
|
"
|
|
@touchend="
|
|
handleH5LongPress($event, item, index, 'touchend')
|
|
"
|
|
@mouseover="
|
|
handleH5LongPress($event, item, index, 'touchend')
|
|
"
|
|
>
|
|
<MessageBubble
|
|
:messageItem="deepCopy(item)"
|
|
:content="item.getMessageContent()"
|
|
:isAudioPlayed="audioPlayedMapping[item.ID]"
|
|
:blinkMessageIDList="blinkMessageIDList"
|
|
:isMultipleSelectMode="isMultipleSelectMode"
|
|
:multipleSelectedMessageIDList="
|
|
multipleSelectedMessageIDList
|
|
"
|
|
@resendMessage="resendMessage(item)"
|
|
@blinkMessage="blinkMessage"
|
|
@scrollTo="scrollTo"
|
|
@changeSelectMessageIDList="changeSelectMessageIDList"
|
|
@setReadReceiptPanelVisible="setReadReceiptPanelVisible"
|
|
>
|
|
<MessageText
|
|
v-if="item.type === TYPES.MSG_TEXT"
|
|
:content="item.getMessageContent()"
|
|
:messageItem="item"
|
|
/>
|
|
<ProgressMessage
|
|
v-else-if="item.type === TYPES.MSG_IMAGE"
|
|
:content="item.getMessageContent()"
|
|
:messageItem="deepCopy(item)"
|
|
>
|
|
<MessageImage
|
|
:content="item.getMessageContent()"
|
|
:messageItem="item"
|
|
@previewImage="handleImagePreview(index)"
|
|
/>
|
|
</ProgressMessage>
|
|
<ProgressMessage
|
|
v-else-if="item.type === TYPES.MSG_VIDEO"
|
|
:content="item.getMessageContent()"
|
|
:messageItem="deepCopy(item)"
|
|
>
|
|
<MessageVideo
|
|
:content="item.getMessageContent()"
|
|
:messageItem="item"
|
|
/>
|
|
</ProgressMessage>
|
|
<MessageAudio
|
|
v-else-if="item.type === TYPES.MSG_AUDIO"
|
|
:content="item.getMessageContent()"
|
|
:messageItem="item"
|
|
:broadcastNewAudioSrc="broadcastNewAudioSrc"
|
|
@setAudioPlayed="setAudioPlayed"
|
|
@getGlobalAudioContext="getGlobalAudioContext"
|
|
/>
|
|
<MessageRecord
|
|
v-else-if="item.type === TYPES.MSG_MERGER"
|
|
:renderData="item.payload"
|
|
:messageItem="item"
|
|
@assignMessageIDInUniapp="assignMessageIDInUniapp"
|
|
/>
|
|
<MessageFile
|
|
v-else-if="item.type === TYPES.MSG_FILE"
|
|
:content="item.getMessageContent()"
|
|
/>
|
|
<MessageFace
|
|
v-else-if="item.type === TYPES.MSG_FACE"
|
|
:content="item.getMessageContent()"
|
|
/>
|
|
<MessageLocation
|
|
v-else-if="item.type === TYPES.MSG_LOCATION"
|
|
:content="item.getMessageContent()"
|
|
/>
|
|
<!-- 自定义消息,目前只支持【红包】 -->
|
|
<MessageCustom
|
|
v-else-if="item.type === TYPES.MSG_CUSTOM"
|
|
:content="item.getMessageContent()"
|
|
:messageItem="item"
|
|
@claim="onClaim(item)"
|
|
/>
|
|
</MessageBubble>
|
|
</div>
|
|
<MessagePlugin
|
|
v-else-if="!item.isRevoked && isPluginMessage(item)"
|
|
:message="item"
|
|
@resendMessage="resendMessage"
|
|
@handleToggleMessageItem="handleToggleMessageItem"
|
|
@handleH5LongPress="handleH5LongPress"
|
|
/>
|
|
<MessageRevoked
|
|
v-else
|
|
:isEdit="item.type === TYPES.MSG_TEXT"
|
|
:messageItem="item"
|
|
@messageEdit="handleEdit(item)"
|
|
/>
|
|
<!-- message tool -->
|
|
<MessageTool
|
|
v-if="item.ID === toggleID"
|
|
:class="{
|
|
'message-tool': true,
|
|
'message-tool-out': item.flow === 'out',
|
|
'message-tool-in': item.flow === 'in'
|
|
}"
|
|
:messageItem="item"
|
|
:isMultipleSelectMode="isMultipleSelectMode"
|
|
@toggleMultipleSelectMode="
|
|
() => emits('toggleMultipleSelectMode')
|
|
"
|
|
/>
|
|
</div>
|
|
</li>
|
|
</scroll-view>
|
|
<!-- scroll button -->
|
|
<ScrollButton
|
|
ref="scrollButtonInstanceRef"
|
|
@scrollToLatestMessage="scrollToLatestMessage"
|
|
/>
|
|
<Dialog
|
|
v-if="reSendDialogShow"
|
|
:show="reSendDialogShow"
|
|
:isH5="!isPC"
|
|
:center="true"
|
|
:isHeaderShow="isPC"
|
|
@submit="resendMessageConfirm()"
|
|
@update:show="e => (reSendDialogShow = e)"
|
|
>
|
|
<p class="delDialog-title">
|
|
{{ TUITranslateService.t('TUIChat.确认重发该消息?') }}
|
|
</p>
|
|
</Dialog>
|
|
<!-- read receipt panel -->
|
|
<ReadReceiptPanel
|
|
v-if="isShowReadUserStatusPanel"
|
|
:message="Object.assign({}, readStatusMessage)"
|
|
@setReadReceiptPanelVisible="setReadReceiptPanelVisible"
|
|
/>
|
|
<!-- simple message list -->
|
|
<Drawer
|
|
:visible="isShowSimpleMessageList"
|
|
:overlayColor="'transparent'"
|
|
:popDirection="'right'"
|
|
>
|
|
<SimpleMessageList
|
|
:style="{ height: '100%' }"
|
|
:isMounted="isShowSimpleMessageList"
|
|
:messageID="simpleMessageListRenderMessageID"
|
|
@closeOverlay="isShowSimpleMessageList = false"
|
|
/>
|
|
</Drawer>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {
|
|
ref,
|
|
watch,
|
|
nextTick,
|
|
onMounted,
|
|
onUnmounted,
|
|
getCurrentInstance
|
|
} from '../../../adapter-vue'
|
|
import TUIChatEngine, {
|
|
IMessageModel,
|
|
TUIStore,
|
|
StoreName,
|
|
TUITranslateService,
|
|
TUIChatService
|
|
} from '@tencentcloud/chat-uikit-engine-lite'
|
|
import {
|
|
setInstanceMapping,
|
|
getBoundingClientRect,
|
|
getScrollInfo
|
|
} from '@tencentcloud/universal-api'
|
|
// import { JoinGroupCard } from '@trtc/calls-uikit-wx';
|
|
import Link from './link'
|
|
import SimpleMessageList from './message-elements/simple-message-list/index.vue'
|
|
import MessageGroupApplication from './message-group-application/index.vue'
|
|
import MessageText from './message-elements/message-text.vue'
|
|
import MessageImage from './message-elements/message-image.vue'
|
|
import MessageAudio from './message-elements/message-audio.vue'
|
|
import MessageRecord from './message-elements/message-record/index.vue'
|
|
import MessageFile from './message-elements/message-file.vue'
|
|
import MessageFace from './message-elements/message-face.vue'
|
|
import MessageCustom from './message-elements/message-custom.vue'
|
|
import MessageTip from './message-elements/message-tip.vue'
|
|
import MessageBubble from './message-elements/message-bubble.vue'
|
|
import MessageLocation from './message-elements/message-location.vue'
|
|
import MessageTimestamp from './message-elements/message-timestamp.vue'
|
|
import MessageVideo from './message-elements/message-video.vue'
|
|
import MessageTool from './message-tool/index.vue'
|
|
import MessageRevoked from './message-tool/message-revoked.vue'
|
|
import MessagePlugin from '../../../plugins/plugin-components/message-plugin.vue'
|
|
import ReadReceiptPanel from './read-receipt-panel/index.vue'
|
|
import ScrollButton from './scroll-button/index.vue'
|
|
import { isPluginMessage } from '../../../plugins/plugin-components/index'
|
|
import Dialog from '../../common/Dialog/index.vue'
|
|
import Drawer from '../../common/Drawer/index.vue'
|
|
import { Toast, TOAST_TYPE } from '../../common/Toast/index'
|
|
import ProgressMessage from '../../common/ProgressMessage/index.vue'
|
|
import { isCreateGroupCustomMessage } from '../utils/utils'
|
|
import {
|
|
isEnabledMessageReadReceiptGlobal,
|
|
deepCopy
|
|
} from '../utils/utils'
|
|
import { throttle } from '../../../utils/lodash'
|
|
import { isPC, isH5, isMobile } from '../../../utils/env'
|
|
import chatStorage from '../utils/chatStorage'
|
|
import { IAudioContext } from '../../../interface'
|
|
import { CHAT_MSG_CUSTOM_TYPE } from '../../../constant'
|
|
import { receiveRedEnvelope } from '../../../../api/tui-kit'
|
|
|
|
interface IEmits {
|
|
(e: 'closeInputToolBar'): void
|
|
(e: 'handleEditor', message: IMessageModel, type: string): void
|
|
(key: 'toggleMultipleSelectMode'): void
|
|
}
|
|
|
|
interface IProps {
|
|
isGroup: boolean
|
|
groupID: string
|
|
isNotInGroup: boolean
|
|
isMultipleSelectMode: boolean
|
|
}
|
|
|
|
const emits = defineEmits<IEmits>()
|
|
const props = withDefaults(defineProps<IProps>(), {
|
|
isGroup: false,
|
|
groupID: '',
|
|
isNotInGroup: false,
|
|
isMultipleSelectMode: false
|
|
})
|
|
|
|
let selfAddValue = 0
|
|
let observer: any = null
|
|
let groupType: string | undefined
|
|
const sentReceiptMessageID = new Set<string>()
|
|
const isOfficial = TUIStore.getData(StoreName.APP, 'isOfficial')
|
|
const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance()
|
|
|
|
const messageList = ref<IMessageModel[]>()
|
|
const multipleSelectedMessageIDList = ref<string[]>([])
|
|
const isCompleted = ref(false)
|
|
const currentConversationID = ref('')
|
|
const toggleID = ref('')
|
|
const scrollTop = ref(5000) // The initial number of messages is 15, and the maximum message height is 300.
|
|
const TYPES = ref(TUIChatEngine.TYPES)
|
|
const isLoadingMessage = ref(false)
|
|
const isLongpressing = ref(false)
|
|
const blinkMessageIDList = ref<string[]>([])
|
|
const messageTarget = ref<IMessageModel>()
|
|
const scrollButtonInstanceRef = ref<InstanceType<typeof ScrollButton>>()
|
|
const historyFirstMessageID = ref<string>('')
|
|
const isShowSimpleMessageList = ref<boolean>(false)
|
|
const simpleMessageListRenderMessageID = ref<string>()
|
|
const audioPlayedMapping = ref<Record<string, boolean>>({})
|
|
|
|
// audio control
|
|
const broadcastNewAudioSrc = ref<string>('')
|
|
|
|
const readStatusMessage = ref<IMessageModel>()
|
|
const isShowReadUserStatusPanel = ref<boolean>(false)
|
|
|
|
// Resend Message Dialog
|
|
const reSendDialogShow = ref(false)
|
|
const resendMessageData = ref()
|
|
|
|
const scrollToBottom = () => {
|
|
scrollTop.value += 300
|
|
// Solve the issue where swiping to the bottom for the first time after packaging Uniapp into an app has a delay,
|
|
// which can be set to 300 ms.
|
|
const timer = setTimeout(() => {
|
|
scrollTop.value += 1
|
|
clearTimeout(timer)
|
|
}, 300)
|
|
}
|
|
|
|
const onCurrentConversationIDUpdated = (conversationID: string) => {
|
|
currentConversationID.value = conversationID
|
|
if (isEnabledMessageReadReceiptGlobal()) {
|
|
const { groupProfile } =
|
|
TUIStore.getConversationModel(conversationID) || {}
|
|
groupType = groupProfile?.type
|
|
}
|
|
|
|
if (Object.keys(audioPlayedMapping.value).length > 0) {
|
|
// Synchronize storage about whether the audio has been played when converstaion switched
|
|
chatStorage.setChatStorage(
|
|
'audioPlayedMapping',
|
|
audioPlayedMapping.value
|
|
)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
// Retrieve the information about whether the audio has been played from localStorage
|
|
audioPlayedMapping.value =
|
|
chatStorage.getChatStorage('audioPlayedMapping') || {}
|
|
|
|
TUIStore.watch(StoreName.CHAT, {
|
|
messageList: onMessageListUpdated,
|
|
messageSource: onMessageSourceUpdated,
|
|
isCompleted: onChatCompletedUpdated
|
|
})
|
|
|
|
TUIStore.watch(StoreName.CONV, {
|
|
currentConversationID: onCurrentConversationIDUpdated
|
|
})
|
|
|
|
setInstanceMapping('messageList', thisInstance)
|
|
|
|
uni.$on('scroll-to-bottom', scrollToLatestMessage)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
TUIStore.unwatch(StoreName.CHAT, {
|
|
messageList: onMessageListUpdated,
|
|
isCompleted: onChatCompletedUpdated
|
|
})
|
|
|
|
TUIStore.unwatch(StoreName.CONV, {
|
|
currentConversationID: onCurrentConversationIDUpdated
|
|
})
|
|
|
|
observer?.disconnect()
|
|
observer = null
|
|
|
|
uni.$off('scroll-to-bottom')
|
|
|
|
if (Object.keys(audioPlayedMapping.value).length > 0) {
|
|
// Synchronize storage about whether the audio has been played when the component is unmounted
|
|
chatStorage.setChatStorage(
|
|
'audioPlayedMapping',
|
|
audioPlayedMapping.value
|
|
)
|
|
}
|
|
})
|
|
|
|
const handelScrollListScroll = throttle(
|
|
function (e: Event) {
|
|
scrollButtonInstanceRef.value?.judgeScrollOverOneScreen(e)
|
|
},
|
|
500,
|
|
{ leading: true }
|
|
)
|
|
|
|
function getGlobalAudioContext(
|
|
audioMap: Map<string, IAudioContext>,
|
|
options?: { newAudioSrc: string }
|
|
) {
|
|
if (options?.newAudioSrc) {
|
|
broadcastNewAudioSrc.value = options.newAudioSrc
|
|
}
|
|
}
|
|
|
|
async function onMessageListUpdated(list: IMessageModel[]) {
|
|
observer?.disconnect()
|
|
messageList.value = list
|
|
.filter(message => !message.isDeleted)
|
|
.map(message => {
|
|
message.vueForRenderKey = `${message.ID}`
|
|
return message
|
|
})
|
|
|
|
// messageList
|
|
const newLastMessage =
|
|
messageList.value?.[messageList.value?.length - 1]
|
|
if (messageTarget.value) {
|
|
// scroll to target message
|
|
scrollAndBlinkMessage(messageTarget.value)
|
|
} else if (
|
|
!isLoadingMessage.value &&
|
|
!(
|
|
scrollButtonInstanceRef.value?.isScrollButtonVisible &&
|
|
newLastMessage?.flow === 'in'
|
|
)
|
|
) {
|
|
// scroll to bottom
|
|
nextTick(() => {
|
|
scrollToBottom()
|
|
})
|
|
}
|
|
if (isEnabledMessageReadReceiptGlobal()) {
|
|
nextTick(() => bindIntersectionObserver())
|
|
}
|
|
}
|
|
|
|
async function scrollToLatestMessage() {
|
|
try {
|
|
const { scrollHeight } = await getScrollInfo(
|
|
'#messageScrollList',
|
|
'messageList'
|
|
)
|
|
if (scrollHeight) {
|
|
scrollTop.value === scrollHeight
|
|
? (scrollTop.value = scrollHeight + 1)
|
|
: (scrollTop.value = scrollHeight)
|
|
} else {
|
|
scrollToBottom()
|
|
}
|
|
} catch (error) {
|
|
scrollToBottom()
|
|
}
|
|
}
|
|
|
|
async function onMessageSourceUpdated(message: IMessageModel) {
|
|
messageTarget.value = message
|
|
scrollAndBlinkMessage(messageTarget.value)
|
|
}
|
|
|
|
function scrollAndBlinkMessage(message: IMessageModel) {
|
|
if (
|
|
messageList.value?.some(
|
|
messageListItem => messageListItem?.ID === message?.ID
|
|
)
|
|
) {
|
|
nextTick(async () => {
|
|
await scrollToTargetMessage(message)
|
|
await blinkMessage(message?.ID)
|
|
messageTarget.value = undefined
|
|
})
|
|
}
|
|
}
|
|
|
|
function onChatCompletedUpdated(flag: boolean) {
|
|
isCompleted.value = flag
|
|
}
|
|
|
|
const getHistoryMessageList = () => {
|
|
isLoadingMessage.value = true
|
|
const currentFirstMessageID = messageList.value?.[0]?.ID || ''
|
|
TUIChatService.getMessageList().then(() => {
|
|
nextTick(() => {
|
|
historyFirstMessageID.value = currentFirstMessageID
|
|
const timer = setTimeout(() => {
|
|
historyFirstMessageID.value = ''
|
|
isLoadingMessage.value = false
|
|
clearTimeout(timer)
|
|
}, 500)
|
|
})
|
|
})
|
|
}
|
|
|
|
const openComplaintLink = () => {}
|
|
|
|
// toggle message
|
|
const handleToggleMessageItem = (
|
|
e: any,
|
|
message: IMessageModel,
|
|
index: number,
|
|
isLongpress = false
|
|
) => {
|
|
if (props.isMultipleSelectMode || props.isNotInGroup) {
|
|
return
|
|
}
|
|
if (isLongpress) {
|
|
isLongpressing.value = true
|
|
}
|
|
toggleID.value = message.ID
|
|
}
|
|
|
|
// h5 long press
|
|
let timer: number
|
|
const handleH5LongPress = (
|
|
e: any,
|
|
message: IMessageModel,
|
|
index: number,
|
|
type: string
|
|
) => {
|
|
if (props.isMultipleSelectMode || props.isNotInGroup) {
|
|
return
|
|
}
|
|
if (!isH5) return
|
|
function longPressHandler() {
|
|
clearTimeout(timer)
|
|
handleToggleMessageItem(e, message, index, true)
|
|
}
|
|
function touchStartHandler() {
|
|
timer = setTimeout(longPressHandler, 500)
|
|
}
|
|
function touchEndHandler() {
|
|
clearTimeout(timer)
|
|
}
|
|
switch (type) {
|
|
case 'touchstart':
|
|
touchStartHandler()
|
|
break
|
|
case 'touchend':
|
|
touchEndHandler()
|
|
setTimeout(() => {
|
|
isLongpressing.value = false
|
|
}, 200)
|
|
break
|
|
}
|
|
}
|
|
|
|
// reedit message
|
|
const handleEdit = (message: IMessageModel) => {
|
|
emits('handleEditor', message, 'reedit')
|
|
}
|
|
|
|
const resendMessage = (message: IMessageModel) => {
|
|
reSendDialogShow.value = true
|
|
resendMessageData.value = message
|
|
}
|
|
|
|
const handleImagePreview = (index: number) => {
|
|
if (!messageList.value) {
|
|
return
|
|
}
|
|
const imageMessageIndex: number[] = []
|
|
const imageMessageList: IMessageModel[] = messageList.value.filter(
|
|
(item, index) => {
|
|
if (
|
|
!item.isRevoked &&
|
|
!item.hasRiskContent &&
|
|
item.type === TYPES.value.MSG_IMAGE
|
|
) {
|
|
imageMessageIndex.push(index)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
)
|
|
uni.previewImage({
|
|
current: imageMessageIndex.indexOf(index),
|
|
urls: imageMessageList.map(
|
|
message => message.payload.imageInfoArray?.[2].url
|
|
),
|
|
// #ifdef APP-PLUS
|
|
indicator: 'number'
|
|
// #endif
|
|
})
|
|
}
|
|
|
|
/** 领取红包 */
|
|
const onClaim = (item: IMessageModel) => {
|
|
const { conversationType, flow, payload } = item
|
|
const data = JSON.parse(payload.data)
|
|
// 群聊
|
|
if (conversationType === TYPES.value.CONV_GROUP) {
|
|
console.log(item)
|
|
console.log(data)
|
|
receiveRedEnvelope({
|
|
redPacketId: data.id
|
|
})
|
|
} else {
|
|
// 个人红包
|
|
// 只能对方领取
|
|
if (flow === 'in') {
|
|
// 修改后的消息
|
|
const newMessage = {
|
|
...data,
|
|
isOpen: true
|
|
}
|
|
item.modifyMessage({
|
|
cloudCustomData: JSON.stringify(newMessage)
|
|
})
|
|
return
|
|
receiveRedEnvelope({
|
|
redPacketId: data.id
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
const resendMessageConfirm = () => {
|
|
reSendDialogShow.value = !reSendDialogShow.value
|
|
const messageModel = resendMessageData.value
|
|
messageModel.resendMessage()
|
|
}
|
|
|
|
function blinkMessage(messageID: string): Promise<void> {
|
|
return new Promise(resolve => {
|
|
const index = blinkMessageIDList.value.indexOf(messageID)
|
|
if (index < 0) {
|
|
blinkMessageIDList.value.push(messageID)
|
|
const timer = setTimeout(() => {
|
|
blinkMessageIDList.value.splice(
|
|
blinkMessageIDList.value.indexOf(messageID),
|
|
1
|
|
)
|
|
clearTimeout(timer)
|
|
resolve()
|
|
}, 3000)
|
|
}
|
|
})
|
|
}
|
|
|
|
function scrollTo(scrollHeight: number) {
|
|
scrollTop.value = scrollHeight
|
|
}
|
|
|
|
async function bindIntersectionObserver() {
|
|
if (!messageList.value || messageList.value.length === 0) {
|
|
return
|
|
}
|
|
|
|
if (
|
|
groupType === TYPES.value.GRP_AVCHATROOM ||
|
|
groupType === TYPES.value.GRP_COMMUNITY
|
|
) {
|
|
// AVCHATROOM and COMMUNITY chats do not monitor read receipts for messages.
|
|
return
|
|
}
|
|
|
|
observer?.disconnect()
|
|
observer = uni
|
|
.createIntersectionObserver(thisInstance, {
|
|
threshold: [0.7],
|
|
observeAll: true
|
|
// In Uni-app, the `safetip` is also included, so a negative margin is needed to exclude it.
|
|
})
|
|
.relativeTo('#messageScrollList', { top: -70 })
|
|
|
|
observer?.observe(
|
|
'.message-li.in .message-bubble-container',
|
|
(res: any) => {
|
|
if (sentReceiptMessageID.has(res.id)) {
|
|
return
|
|
}
|
|
const matchingMessage = messageList.value.find(
|
|
(message: IMessageModel) => {
|
|
return res.id.indexOf(message.ID) > -1
|
|
}
|
|
)
|
|
if (
|
|
matchingMessage &&
|
|
matchingMessage.needReadReceipt &&
|
|
matchingMessage.flow === 'in' &&
|
|
!matchingMessage.readReceiptInfo?.isPeerRead
|
|
) {
|
|
TUIChatService.sendMessageReadReceipt([matchingMessage])
|
|
sentReceiptMessageID.add(res.id)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
function setReadReceiptPanelVisible(
|
|
visible: boolean,
|
|
message?: IMessageModel
|
|
) {
|
|
if (visible && props.isNotInGroup) {
|
|
return
|
|
}
|
|
if (!visible) {
|
|
readStatusMessage.value = undefined
|
|
} else {
|
|
readStatusMessage.value = message
|
|
}
|
|
isShowReadUserStatusPanel.value = visible
|
|
}
|
|
|
|
async function scrollToTargetMessage(message: IMessageModel) {
|
|
const targetMessageID = message.ID
|
|
const isTargetMessageInScreen =
|
|
messageList.value &&
|
|
messageList.value.some(msg => msg.ID === targetMessageID)
|
|
if (targetMessageID && isTargetMessageInScreen) {
|
|
const timer = setTimeout(async () => {
|
|
try {
|
|
const scrollViewRect = await getBoundingClientRect(
|
|
'#messageScrollList',
|
|
'messageList'
|
|
)
|
|
const originalMessageRect = await getBoundingClientRect(
|
|
'#tui-' + targetMessageID,
|
|
'messageList'
|
|
)
|
|
const { scrollTop } = await getScrollInfo(
|
|
'#messageScrollList',
|
|
'messageList'
|
|
)
|
|
const finalScrollTop =
|
|
originalMessageRect.top +
|
|
scrollTop -
|
|
scrollViewRect.top -
|
|
(selfAddValue++ % 2)
|
|
scrollTo(finalScrollTop)
|
|
clearTimeout(timer)
|
|
} catch (error) {
|
|
// todo
|
|
}
|
|
}, 500)
|
|
} else {
|
|
Toast({
|
|
message: TUITranslateService.t('TUIChat.无法定位到原消息'),
|
|
type: TOAST_TYPE.WARNING
|
|
})
|
|
}
|
|
}
|
|
|
|
function onMessageListBackgroundClick() {
|
|
emits('closeInputToolBar')
|
|
}
|
|
|
|
watch(
|
|
() => props.isMultipleSelectMode,
|
|
newValue => {
|
|
if (!newValue) {
|
|
changeSelectMessageIDList({
|
|
type: 'clearAll',
|
|
messageID: ''
|
|
})
|
|
}
|
|
}
|
|
)
|
|
|
|
function changeSelectMessageIDList({
|
|
type,
|
|
messageID
|
|
}: {
|
|
type: 'add' | 'remove' | 'clearAll'
|
|
messageID: string
|
|
}) {
|
|
// TODO need to delete this
|
|
if (type === 'clearAll') {
|
|
multipleSelectedMessageIDList.value = []
|
|
} else if (
|
|
type === 'add' &&
|
|
!multipleSelectedMessageIDList.value.includes(messageID)
|
|
) {
|
|
multipleSelectedMessageIDList.value.push(messageID)
|
|
} else if (type === 'remove') {
|
|
multipleSelectedMessageIDList.value =
|
|
multipleSelectedMessageIDList.value.filter(id => id !== messageID)
|
|
}
|
|
}
|
|
|
|
function mergeForwardMessage() {
|
|
TUIStore.update(StoreName.CUSTOM, 'multipleForwardMessageID', {
|
|
isMergeForward: true,
|
|
messageIDList: multipleSelectedMessageIDList.value
|
|
})
|
|
}
|
|
|
|
function oneByOneForwardMessage() {
|
|
TUIStore.update(StoreName.CUSTOM, 'multipleForwardMessageID', {
|
|
isMergeForward: false,
|
|
messageIDList: multipleSelectedMessageIDList.value
|
|
})
|
|
}
|
|
|
|
function assignMessageIDInUniapp(messageID: string) {
|
|
simpleMessageListRenderMessageID.value = messageID
|
|
isShowSimpleMessageList.value = true
|
|
}
|
|
|
|
function setAudioPlayed(messageID: string) {
|
|
audioPlayedMapping.value[messageID] = true
|
|
}
|
|
|
|
defineExpose({
|
|
oneByOneForwardMessage,
|
|
mergeForwardMessage,
|
|
scrollToLatestMessage
|
|
})
|
|
</script>
|
|
<style lang="scss" scoped src="./style/index.scss"></style>
|