665 lines
17 KiB
Vue
665 lines
17 KiB
Vue
<template>
|
|
<div
|
|
:class="`msg-item-wrapper ${
|
|
props.msg.pinState &&
|
|
!(
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM &&
|
|
props.msg.timeValue !== undefined
|
|
) &&
|
|
!props.msg.recallType
|
|
? 'msg-pin'
|
|
: ''
|
|
}`"
|
|
:id="MSG_ID_FLAG + props.msg.messageClientId"
|
|
:key="props.msg.createTime"
|
|
>
|
|
<!-- 消息时间 -->
|
|
<div
|
|
class="msg-time"
|
|
v-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM &&
|
|
props.msg.timeValue !== undefined
|
|
"
|
|
>
|
|
{{ props.msg.timeValue }}
|
|
</div>
|
|
<!-- 撤回消息 可重新编辑 -->
|
|
<div
|
|
class="msg-common"
|
|
:style="{
|
|
flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse',
|
|
}"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM &&
|
|
props.msg.recallType === 'reCallMsg' &&
|
|
props.msg.canEdit
|
|
"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<MessageBubble :msg="props.msg" :bg-visible="true">
|
|
{{ t('recall2') }}
|
|
<text
|
|
class="msg-recall-btn"
|
|
@tap="
|
|
() => {
|
|
handleReeditMsg(props.msg)
|
|
}
|
|
"
|
|
>
|
|
{{ t('reeditText') }}
|
|
</text>
|
|
</MessageBubble>
|
|
</div>
|
|
<!-- 撤回消息 不可重新编辑 主动撤回 -->
|
|
<div
|
|
class="msg-common"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM &&
|
|
props.msg.recallType === 'reCallMsg' &&
|
|
!props.msg.canEdit
|
|
"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<MessageBubble :msg="props.msg" :bg-visible="true">
|
|
<div class="recall-text">{{ t('you') + t('recall') }}</div>
|
|
</MessageBubble>
|
|
</div>
|
|
<!-- 撤回消息 对方撤回-->
|
|
<div
|
|
class="msg-common"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM &&
|
|
props.msg.recallType === 'beReCallMsg'
|
|
"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<div :class="props.msg.isSelf ? 'self-msg-recall' : 'msg-recall'">
|
|
<text class="msg-recall2">
|
|
{{ !props.msg.isSelf ? t('recall2') : `${t('you') + t('recall')}` }}
|
|
</text>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 文本消息-->
|
|
<div
|
|
class="msg-common"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT
|
|
"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="true"
|
|
>
|
|
<ReplyMessage v-if="!!replyMsg" :replyMsg="replyMsg"></ReplyMessage>
|
|
<MessageText :msg="props.msg"></MessageText>
|
|
</MessageBubble>
|
|
</div>
|
|
<MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead>
|
|
</div>
|
|
<!-- 图片消息-->
|
|
<div
|
|
class="msg-common"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE
|
|
"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="true"
|
|
style="cursor: pointer"
|
|
>
|
|
<div
|
|
@tap="
|
|
() => {
|
|
//@ts-ignore
|
|
handleImageTouch(props.msg.attachment?.url)
|
|
}
|
|
"
|
|
>
|
|
<image
|
|
class="msg-image"
|
|
:lazy-load="true"
|
|
mode="aspectFill"
|
|
:src="imageUrl"
|
|
></image>
|
|
</div>
|
|
</MessageBubble>
|
|
</div>
|
|
<MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead>
|
|
</div>
|
|
<!-- 视频消息-->
|
|
<div
|
|
class="msg-common"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO
|
|
"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="true"
|
|
style="cursor: pointer"
|
|
>
|
|
<div
|
|
class="video-msg-wrapper"
|
|
@tap="() => handleVideoTouch(props.msg)"
|
|
>
|
|
<div class="video-play-button">
|
|
<div class="video-play-icon"></div>
|
|
</div>
|
|
<image
|
|
class="msg-image"
|
|
:lazy-load="true"
|
|
mode="aspectFill"
|
|
:src="videoFirstFrameDataUrl"
|
|
></image>
|
|
</div>
|
|
</MessageBubble>
|
|
</div>
|
|
<MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead>
|
|
</div>
|
|
<!-- 音视频消息-->
|
|
<div
|
|
class="msg-common"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL
|
|
"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="true"
|
|
>
|
|
<MessageG2 :msg="props.msg" />
|
|
</MessageBubble>
|
|
</div>
|
|
</div>
|
|
<!-- 文件消息-->
|
|
<div
|
|
class="msg-common"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_FILE
|
|
"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="false"
|
|
>
|
|
<MessageFile :msg="props.msg" />
|
|
</MessageBubble>
|
|
</div>
|
|
<MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead>
|
|
</div>
|
|
<!-- 语音消息-->
|
|
<div
|
|
class="msg-common"
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO
|
|
"
|
|
:style="{
|
|
flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse',
|
|
}"
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="true"
|
|
style="cursor: pointer"
|
|
>
|
|
<MessageAudio
|
|
:msg="props.msg"
|
|
:broadcastNewAudioSrc="broadcastNewAudioSrc"
|
|
/>
|
|
</MessageBubble>
|
|
</div>
|
|
<!-- <MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead> -->
|
|
</div>
|
|
<!-- 通知消息-->
|
|
<MessageNotification
|
|
v-else-if="
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION
|
|
"
|
|
:msg="props.msg"
|
|
/>
|
|
<div
|
|
class="msg-common"
|
|
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
|
v-else
|
|
>
|
|
<Avatar
|
|
:account="props.msg.senderId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
:goto-user-card="true"
|
|
/>
|
|
<div class="msg-content">
|
|
<div class="msg-name" v-if="!props.msg.isSelf">
|
|
{{ appellation }}
|
|
</div>
|
|
<MessageBubble
|
|
:msg="props.msg"
|
|
:tooltip-visible="true"
|
|
:bg-visible="true"
|
|
>
|
|
[{{ t('unknowMsgText') }}]
|
|
</MessageBubble>
|
|
</div>
|
|
</div>
|
|
<!-- 消息标记 不展示 pinState 为 0 、时间消息以及撤回消息的标记样式 -->
|
|
<div
|
|
class="msg-pin-tip"
|
|
v-if="
|
|
props.msg.pinState &&
|
|
!(
|
|
props.msg.messageType ===
|
|
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM &&
|
|
props.msg.timeValue !== undefined
|
|
) &&
|
|
!props.msg.recallType
|
|
"
|
|
:style="{ justifyContent: !props.msg.isSelf ? 'flex-start' : 'flex-end' }"
|
|
>
|
|
<Icon :size="11" type="icon-green-pin"></Icon> <span
|
|
v-if="props.msg.operatorId === accountId"
|
|
>{{ `${t('you')}` }}</span
|
|
>
|
|
<Appellation
|
|
v-else
|
|
:account="props.msg.operatorId"
|
|
:teamId="
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: ''
|
|
"
|
|
color="#3EAF96"
|
|
fontSize="11"
|
|
></Appellation
|
|
> {{ `${t('pinThisText')}` }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
/** 消息组件 */
|
|
import { ref, computed, onUnmounted } from 'vue'
|
|
import Avatar from '@/components/Avatar.vue'
|
|
import MessageBubble from './message-bubble.vue'
|
|
import ReplyMessage from './message-reply.vue'
|
|
import MessageFile from './message-file.vue'
|
|
import MessageText from './message-text.vue'
|
|
import MessageAudio from './message-audio.vue'
|
|
import MessageNotification from './message-notification.vue'
|
|
import MessageG2 from './message-g2.vue'
|
|
import { customNavigateTo } from '@/utils/im/customNavigate'
|
|
import MessageIsRead from './message-read.vue'
|
|
import Icon from '@/components/Icon.vue'
|
|
import Appellation from '@/components/Appellation.vue'
|
|
import { events, MSG_ID_FLAG } from '@/utils/im/constants'
|
|
import { autorun } from 'mobx'
|
|
import { stopAllAudio } from '@/utils/im/index'
|
|
import { t } from '@/utils/im/i18n'
|
|
import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types'
|
|
import { V2NIMConst } from '@/utils/im/nim'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
msg: V2NIMMessageForUI & { timeValue?: number }
|
|
index: number
|
|
replyMsgsMap?: {
|
|
[key: string]: V2NIMMessageForUI
|
|
}
|
|
broadcastNewAudioSrc: string
|
|
}>(),
|
|
{}
|
|
)
|
|
|
|
/** 回复消息 */
|
|
const replyMsg = computed(() => {
|
|
return props.replyMsgsMap && props.replyMsgsMap[props.msg.messageClientId]
|
|
})
|
|
|
|
/** 昵称 */
|
|
const appellation = ref('')
|
|
/** 当前用户账号 */
|
|
const accountId = uni.$UIKitStore?.userStore?.myUserInfo.accountId
|
|
|
|
/** 会话类型 */
|
|
const conversationType =
|
|
uni.$UIKitNIM.V2NIMConversationIdUtil.parseConversationType(
|
|
props.msg.conversationId
|
|
) as unknown as V2NIMConst.V2NIMConversationType
|
|
/** 会话对象 */
|
|
const to = uni.$UIKitNIM.V2NIMConversationIdUtil.parseConversationTargetId(
|
|
props.msg.conversationId
|
|
)
|
|
|
|
/** 获取视频首帧 */
|
|
const videoFirstFrameDataUrl = computed(() => {
|
|
//@ts-ignore
|
|
const url = props.msg.attachment?.url
|
|
return url ? `${url}${url.includes('?') ? '&' : '?'}vframe&offset=1` : ''
|
|
})
|
|
|
|
/** 图片地址 */
|
|
const imageUrl = computed(() => {
|
|
/** 被拉黑 */
|
|
if (props.msg.messageStatus.errorCode == 102426) {
|
|
return 'https://yx-web-nosdn.netease.im/common/c1f278b963b18667ecba4ee9a6e68047/img-fail.png'
|
|
}
|
|
|
|
/** 非好友关系 */
|
|
if (props.msg.messageStatus.errorCode == 104404) {
|
|
return 'https://yx-web-nosdn.netease.im/common/c1f278b963b18667ecba4ee9a6e68047/img-fail.png'
|
|
}
|
|
|
|
//@ts-ignore
|
|
return props.msg?.attachment?.url || props.msg.attachment?.file
|
|
})
|
|
|
|
/** 点击图片预览 */
|
|
const handleImageTouch = (url: string) => {
|
|
if (url) {
|
|
uni.previewImage({
|
|
urls: [url],
|
|
})
|
|
}
|
|
}
|
|
|
|
/** 点击视频播放 */
|
|
const handleVideoTouch = (msg: V2NIMMessageForUI) => {
|
|
stopAllAudio()
|
|
//@ts-ignore
|
|
const url = msg.attachment?.url
|
|
if (url) {
|
|
customNavigateTo({
|
|
url: `/pages/Chat/video-play?videoUrl=${encodeURIComponent(url)}`,
|
|
})
|
|
}
|
|
}
|
|
|
|
/** 重新编辑消息 */
|
|
const handleReeditMsg = (msg: V2NIMMessageForUI) => {
|
|
uni.$emit(events.ON_REEDIT_MSG, msg)
|
|
}
|
|
|
|
/** 监听昵称变化 */
|
|
const appellationWatch = autorun(() => {
|
|
/** 昵称展示顺序 群昵称 > 备注 > 个人昵称 > 帐号 */
|
|
appellation.value = uni.$UIKitStore.uiStore.getAppellation({
|
|
account: props.msg.senderId,
|
|
teamId:
|
|
conversationType ===
|
|
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
|
? to
|
|
: '',
|
|
})
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
appellationWatch()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.msg-item-wrapper {
|
|
padding: 0 15px 15px;
|
|
}
|
|
|
|
.msg-common {
|
|
padding-top: 8px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
font-size: 16px;
|
|
message-is-read {
|
|
align-self: flex-end;
|
|
}
|
|
}
|
|
.msg-pin {
|
|
opacity: 1;
|
|
background: #fffbea;
|
|
}
|
|
.msg-pin-tip {
|
|
font-size: 11px;
|
|
font-weight: normal;
|
|
color: #3eaf96;
|
|
margin: 8px 50px 0 50px;
|
|
display: flex;
|
|
align-items: center;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.msg-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.msg-name {
|
|
font-size: 12px;
|
|
color: #999;
|
|
text-align: left;
|
|
margin-bottom: 4px;
|
|
max-width: 300rpx;
|
|
padding-left: 8px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.msg-image {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.msg-time {
|
|
margin-top: 8px;
|
|
text-align: center;
|
|
color: #b3b7bc;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.msg-recall-btn {
|
|
margin-left: 5px;
|
|
color: #1861df;
|
|
}
|
|
|
|
.msg-recall2 {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.self-msg-recall {
|
|
max-width: 360rpx;
|
|
overflow: hidden;
|
|
padding: 12px 16px;
|
|
border-radius: 8px 0px 8px 8px;
|
|
margin-right: 8px;
|
|
background-color: #d6e5f6;
|
|
color: #666666;
|
|
}
|
|
|
|
.msg-recall {
|
|
max-width: 360rpx;
|
|
overflow: hidden;
|
|
padding: 12px 16px;
|
|
border-radius: 0px 8px 8px 8px;
|
|
margin-left: 8px;
|
|
background-color: #e8eaed;
|
|
color: #666666;
|
|
}
|
|
|
|
.recall-text {
|
|
color: #666666;
|
|
}
|
|
|
|
.video-play-button {
|
|
width: 50px;
|
|
height: 50px;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
border-radius: 50%;
|
|
z-index: 9;
|
|
}
|
|
|
|
.video-play-icon {
|
|
width: 0;
|
|
height: 0;
|
|
border-top: 10px solid transparent;
|
|
border-bottom: 10px solid transparent;
|
|
border-left: 18px solid #fff;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-40%, -50%);
|
|
}
|
|
|
|
.video-msg-wrapper {
|
|
box-sizing: border-box;
|
|
max-width: 360rpx;
|
|
}
|
|
</style>
|