UI优化,红包领取后缺少头像,名称字段

This commit is contained in:
bobobobo
2026-01-19 23:30:08 +08:00
parent d2a22b9419
commit 651d20b909
41 changed files with 2189 additions and 1827 deletions

View File

@@ -103,4 +103,9 @@
}
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.tui-navigation {
border-bottom: 2rpx solid #0000000a;
box-sizing: border-box;
}
</style>

View File

@@ -40,7 +40,7 @@
}
.message-input-toolbar-uni {
background-color: #ebf0f6;
background-color: #ffffff;
flex-direction: column;
z-index: 100;

View File

@@ -1,5 +1,15 @@
@import "../../../../../assets/styles/common";
@import "./color";
@import "./web";
@import "./h5";
@import "./uni";
@import '../../../../../assets/styles/common';
@import './color';
@import './web';
@import './h5';
@import './uni';
.toolbar-item-container-icon {
background: #f4f4f4;
}
.toolbar-item-container-uni-title {
font-weight: 500;
font-size: 24rpx;
color: #666666;
}

View File

@@ -212,7 +212,7 @@ defineExpose({
flex-direction: column;
border: none;
overflow: hidden;
background: #ebf0f6;
background: #ffffff;
&-h5 {
padding: 10px 10px 15px;

View File

@@ -2,22 +2,21 @@
<div
:class="{
'message-input-container': true,
'message-input-container-h5': !isPC,
'message-input-container-h5': !isPC
}"
>
<div
v-if="props.isMuted"
class="message-input-mute"
>
<div v-if="props.isMuted" class="message-input-mute">
{{ props.muteText }}
</div>
<!-- #ifdef APP-PLUS -->
<div
v-if="inputToolbarDisplayType === 'emojiPicker' || inputToolbarDisplayType === 'tools'"
v-if="
inputToolbarDisplayType === 'emojiPicker' ||
inputToolbarDisplayType === 'tools'
"
class="input-click-mask"
@tap.stop.prevent="handleMaskClick"
>
</div>
></div>
<!-- #endif -->
<input
id="editor"
@@ -27,7 +26,7 @@
cursor-spacing="20"
confirm-type="send"
:confirm-hold="true"
:focus="programmaticFocus"
:focus="programmaticFocus"
maxlength="140"
type="text"
placeholder-class="input-placeholder"
@@ -38,284 +37,314 @@
@input="onInput"
@blur="onBlur"
@focus="onFocus"
>
/>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
import { TUIStore, StoreName, IConversationModel, IMessageModel } from '@tencentcloud/chat-uikit-engine-lite';
import { TUIGlobal } from '@tencentcloud/universal-api';
import DraftManager from '../utils/conversationDraft';
import { transformTextWithEmojiNamesToKeys } from '../emoji-config';
import { isPC } from '../../../utils/env';
import { sendMessages } from '../utils/sendMessage';
import { ISendMessagePayload, ToolbarDisplayType } from '../../../interface';
import {
ref,
watch,
onMounted,
onUnmounted
} from '../../../adapter-vue'
import {
TUIStore,
StoreName,
IConversationModel,
IMessageModel
} from '@tencentcloud/chat-uikit-engine-lite'
import { TUIGlobal } from '@tencentcloud/universal-api'
import DraftManager from '../utils/conversationDraft'
import { transformTextWithEmojiNamesToKeys } from '../emoji-config'
import { isPC } from '../../../utils/env'
import { sendMessages } from '../utils/sendMessage'
import {
ISendMessagePayload,
ToolbarDisplayType
} from '../../../interface'
const props = defineProps({
placeholder: {
type: String,
default: 'this is placeholder',
},
replayOrReferenceMessage: {
type: Object,
default: () => ({}),
required: false,
},
isMuted: {
type: Boolean,
default: true,
},
muteText: {
type: String,
default: '',
},
enableInput: {
type: Boolean,
default: true,
},
enableAt: {
type: Boolean,
default: true,
},
enableTyping: {
type: Boolean,
default: true,
},
isGroup: {
type: Boolean,
default: false,
},
inputToolbarDisplayType: {
type: String,
defult: '',
}
});
const emits = defineEmits(['onTyping', 'onFocus', 'onAt']);
const inputText = ref('');
const inputRef = ref();
const inputBlur = ref(true);
const programmaticFocus = ref(false);
const inputContentEmpty = ref(true);
const allInsertedAtInfo = new Map();
const currentConversation = ref<IConversationModel>();
const currentConversationID = ref<string>('');
const currentQuoteMessage = ref<{ message: IMessageModel; type: string }>();
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.watch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
uni.$on('insert-emoji', (data) => {
inputText.value += data?.emoji?.name;
});
uni.$on('send-message-in-emoji-picker', () => {
handleSendMessage();
});
});
onUnmounted(() => {
if (currentConversationID.value) {
DraftManager.setStore(currentConversationID.value, inputText.value, inputText.value, currentQuoteMessage.value);
}
uni.$off('insertEmoji');
uni.$off('send-message-in-emoji-picker');
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.unwatch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
reset();
});
const handleSendMessage = () => {
const messageList = getEditorContent();
resetEditor();
sendMessages(messageList as any, currentConversation.value!);
};
const insertAt = (atInfo: any) => {
if (!allInsertedAtInfo?.has(atInfo?.id)) {
allInsertedAtInfo?.set(atInfo?.id, atInfo?.label);
}
inputText.value += atInfo?.label;
};
const getEditorContent = () => {
let text = inputText.value;
text = transformTextWithEmojiNamesToKeys(text);
const atUserList: string[] = [];
allInsertedAtInfo?.forEach((value: string, key: string) => {
if (text?.includes('@' + value)) {
atUserList.push(key);
}
});
const payload: ISendMessagePayload = {
text,
};
if (atUserList?.length) {
payload.atUserList = atUserList;
}
return [
{
type: 'text',
payload,
const props = defineProps({
placeholder: {
type: String,
default: 'this is placeholder'
},
];
};
const resetEditor = () => {
inputText.value = '';
inputContentEmpty.value = true;
allInsertedAtInfo?.clear();
};
const setEditorContent = (content: any) => {
inputText.value = content;
};
const onBlur = () => {
inputBlur.value = true;
programmaticFocus.value = false;
};
const onFocus = (e: any) => {
inputBlur.value = false;
emits('onFocus', e?.detail?.height);
uni.$emit('scroll-to-bottom');
};
const isEditorContentEmpty = () => {
inputContentEmpty.value = inputText?.value?.length ? false : true;
};
const onInput = (e: any) => {
// uni-app recognizes mention messages
const text = e?.detail?.value;
isEditorContentEmpty();
if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
TUIGlobal?.hideKeyboard();
emits('onAt', true);
}
};
watch(
() => [inputContentEmpty.value, inputBlur.value],
(newVal: any, oldVal: any) => {
if (newVal !== oldVal) {
emits('onTyping', inputContentEmpty.value, inputBlur.value);
replayOrReferenceMessage: {
type: Object,
default: () => ({}),
required: false
},
isMuted: {
type: Boolean,
default: true
},
muteText: {
type: String,
default: ''
},
enableInput: {
type: Boolean,
default: true
},
enableAt: {
type: Boolean,
default: true
},
enableTyping: {
type: Boolean,
default: true
},
isGroup: {
type: Boolean,
default: false
},
inputToolbarDisplayType: {
type: String,
defult: ''
}
},
{
immediate: true,
deep: true,
},
);
})
function onCurrentConversationUpdated(conversation: IConversationModel) {
const prevConversationID = currentConversationID.value;
currentConversation.value = conversation;
currentConversationID.value = conversation?.conversationID;
if (prevConversationID !== currentConversationID.value) {
if (prevConversationID) {
DraftManager.setStore(
prevConversationID,
inputText.value,
inputText.value,
currentQuoteMessage.value,
);
}
resetEditor();
const emits = defineEmits(['onTyping', 'onFocus', 'onAt'])
const inputText = ref('')
const inputRef = ref()
const inputBlur = ref(true)
const programmaticFocus = ref(false)
const inputContentEmpty = ref(true)
const allInsertedAtInfo = new Map()
const currentConversation = ref<IConversationModel>()
const currentConversationID = ref<string>('')
const currentQuoteMessage = ref<{
message: IMessageModel
type: string
}>()
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated
})
TUIStore.watch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated
})
uni.$on('insert-emoji', data => {
inputText.value += data?.emoji?.name
})
uni.$on('send-message-in-emoji-picker', () => {
handleSendMessage()
})
})
onUnmounted(() => {
if (currentConversationID.value) {
DraftManager.getStore(currentConversationID.value, setEditorContent);
DraftManager.setStore(
currentConversationID.value,
inputText.value,
inputText.value,
currentQuoteMessage.value
)
}
uni.$off('insertEmoji')
uni.$off('send-message-in-emoji-picker')
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated
})
TUIStore.unwatch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated
})
reset()
})
const handleSendMessage = () => {
const messageList = getEditorContent()
resetEditor()
sendMessages(messageList as any, currentConversation.value!)
}
const insertAt = (atInfo: any) => {
if (!allInsertedAtInfo?.has(atInfo?.id)) {
allInsertedAtInfo?.set(atInfo?.id, atInfo?.label)
}
inputText.value += atInfo?.label
}
const getEditorContent = () => {
let text = inputText.value
text = transformTextWithEmojiNamesToKeys(text)
const atUserList: string[] = []
allInsertedAtInfo?.forEach((value: string, key: string) => {
if (text?.includes('@' + value)) {
atUserList.push(key)
}
})
const payload: ISendMessagePayload = {
text
}
if (atUserList?.length) {
payload.atUserList = atUserList
}
return [
{
type: 'text',
payload
}
]
}
const resetEditor = () => {
inputText.value = ''
inputContentEmpty.value = true
allInsertedAtInfo?.clear()
}
const setEditorContent = (content: any) => {
inputText.value = content
}
const onBlur = () => {
inputBlur.value = true
programmaticFocus.value = false
}
const onFocus = (e: any) => {
inputBlur.value = false
emits('onFocus', e?.detail?.height)
uni.$emit('scroll-to-bottom')
}
const isEditorContentEmpty = () => {
inputContentEmpty.value = inputText?.value?.length ? false : true
}
const onInput = (e: any) => {
// uni-app recognizes mention messages
const text = e?.detail?.value
isEditorContentEmpty()
if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
TUIGlobal?.hideKeyboard()
emits('onAt', true)
}
}
}
function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
currentQuoteMessage.value = options;
}
watch(
() => [inputContentEmpty.value, inputBlur.value],
(newVal: any, oldVal: any) => {
if (newVal !== oldVal) {
emits('onTyping', inputContentEmpty.value, inputBlur.value)
}
},
{
immediate: true,
deep: true
}
)
function reset() {
inputBlur.value = true;
currentConversation.value = null;
currentConversationID.value = '';
currentQuoteMessage.value = null;
resetEditor();
}
function onCurrentConversationUpdated(
conversation: IConversationModel
) {
const prevConversationID = currentConversationID.value
currentConversation.value = conversation
currentConversationID.value = conversation?.conversationID
if (prevConversationID !== currentConversationID.value) {
if (prevConversationID) {
DraftManager.setStore(
prevConversationID,
inputText.value,
inputText.value,
currentQuoteMessage.value
)
}
resetEditor()
if (currentConversationID.value) {
DraftManager.getStore(
currentConversationID.value,
setEditorContent
)
}
}
}
function onQuoteMessageUpdated(options?: {
message: IMessageModel
type: string
}) {
currentQuoteMessage.value = options
}
function handleMaskClick(e: Event) {
// #ifdef APP-PLUS
e.stopPropagation();
emits('onFocus');
uni.$emit('scroll-to-bottom');
setTimeout(() => {
programmaticFocus.value = true;
// IOS set 500ms timeout
}, 100);
// #endif
}
defineExpose({
insertAt,
resetEditor,
setEditorContent,
getEditorContent,
});
function reset() {
inputBlur.value = true
currentConversation.value = null
currentConversationID.value = ''
currentQuoteMessage.value = null
resetEditor()
}
function handleMaskClick(e: Event) {
// #ifdef APP-PLUS
e.stopPropagation()
emits('onFocus')
uni.$emit('scroll-to-bottom')
setTimeout(() => {
programmaticFocus.value = true
// IOS set 500ms timeout
}, 100)
// #endif
}
defineExpose({
insertAt,
resetEditor,
setEditorContent,
getEditorContent
})
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/common";
@import '../../../assets/styles/common';
.message-input-container {
display: flex;
flex-direction: column;
flex: 1;
padding: 3px 10px 10px;
overflow: hidden;
position: relative;
&-h5 {
flex: 1;
height: auto;
background: #fff;
border-radius: 10px;
padding: 7px 0 7px 10px;
font-size: 16px !important;
max-height: 86px;
}
.message-input-mute {
flex: 1;
.message-input-container {
display: flex;
color: #999;
font-size: 14px;
justify-content: center;
align-items: center;
}
.message-input-area {
flex-direction: column;
flex: 1;
overflow-y: scroll;
min-height: 25px;
}
}
padding: 3px 10px 10px;
overflow: hidden;
position: relative;
border-radius: 64rpx !important;
background: #f4f4f4 !important;
.input-click-mask {
background-color: transparent;
position: absolute;
inset: 0;
z-index: 1;
}
&-h5 {
flex: 1;
height: auto;
background: #fff;
border-radius: 10px;
padding: 7px 0 7px 10px;
font-size: 16px !important;
max-height: 86px;
}
.message-input-mute {
flex: 1;
display: flex;
color: #999;
font-size: 14px;
justify-content: center;
align-items: center;
}
.message-input-area {
flex: 1;
overflow-y: scroll;
min-height: 25px;
}
}
.input-click-mask {
background-color: transparent;
position: absolute;
inset: 0;
z-index: 1;
}
</style>

View File

@@ -616,16 +616,35 @@
const onClaim = (item: IMessageModel, index: number) => {
const { conversationType, cloudCustomData, 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 {
// 个人红包
getRedEnvelopeDetail(data.id).then(async (res: any) => {
getRedEnvelopeDetail(data.id).then(async (res: any) => {
// 群聊
if (conversationType === TYPES.value.CONV_GROUP) {
if (res.data.hasReceived) {
// 直接去详情页
navigateTo('/pages/red-packet/details', {
id: data.id,
type: conversationType
})
} else {
const show = await showDialog('提示', '是否领取该红包?')
if (show) {
// newMessage.in = true
// customRefMessage.value[index].updateClaimStatus(newMessage)
// item.modifyMessage({
// cloudCustomData: JSON.stringify(newMessage)
// })
await receiveRedEnvelope({
redPacketId: data.id
})
navigateTo('/pages/red-packet/details', {
id: data.id,
type: conversationType
})
}
}
} else {
// 个人红包
console.log(res)
let newMessage = {
// ...data,
@@ -639,23 +658,37 @@
if (flow === 'in') {
if (res.data.hasReceived) {
// 直接去详情页
navigateTo('/pages/red-packet/details', {
id: data.id,
type: conversationType
})
} else {
const show = await showDialog('提示', '是否领取该红包?')
if (show) {
newMessage.in = true
customRefMessage.value[index].updateClaimStatus(newMessage)
item.modifyMessage({
cloudCustomData: JSON.stringify(newMessage)
// newMessage.in = true
// customRefMessage.value[index].updateClaimStatus(newMessage)
// item.modifyMessage({
// cloudCustomData: JSON.stringify(newMessage)
// })
await receiveRedEnvelope({
redPacketId: data.id
})
navigateTo('/pages/red-packet/details', {
id: data.id,
type: conversationType
})
}
}
} else {
newMessage.out = true
customRefMessage.value[index].updateClaimStatus(newMessage)
item.modifyMessage({
cloudCustomData: JSON.stringify(newMessage)
navigateTo('/pages/red-packet/details', {
id: data.id,
type: conversationType
})
// newMessage.out = true
// customRefMessage.value[index].updateClaimStatus(newMessage)
// item.modifyMessage({
// cloudCustomData: JSON.stringify(newMessage)
// })
// .then(() => {
// navigateTo('/pages/red-packet/details', {
// id: data.id,
@@ -663,23 +696,8 @@
// })
// })
}
})
return
if (flow === 'in') {
// 修改后的消息
const newMessage = {
...data,
isOpen: true
}
item.modifyMessage({
cloudCustomData: JSON.stringify(newMessage)
})
receiveRedEnvelope({
redPacketId: data.id
})
}
}
})
}
const resendMessageConfirm = () => {

View File

@@ -21,19 +21,24 @@
<Avatar
useSkeletonAnimation
:url="message.avatar || ''"
:style="{flex: '0 0 auto'}"
:style="{ flex: '0 0 auto' }"
/>
<main
class="message-body"
@click.stop
>
<main class="message-body" @click.stop>
<div
v-if="message.flow === 'in' && message.conversationType === 'GROUP'"
v-if="
message.flow === 'in' &&
message.conversationType === 'GROUP'
"
class="message-body-nick-name"
>
{{ props.content.showName }}
</div>
<div :class="['message-body-main', message.flow === 'out' && 'message-body-main-reverse']">
<div
:class="[
'message-body-main',
message.flow === 'out' && 'message-body-main-reverse'
]"
>
<div
:class="[
'blink',
@@ -42,18 +47,22 @@
message.hasRiskContent && 'content-has-risk',
isNoPadding ? 'content-no-padding' : '',
isNoPadding && isBlink ? 'blink-shadow' : '',
!isNoPadding && isBlink ? 'blink-content' : '',
!isNoPadding && isBlink ? 'blink-content' : ''
]"
>
<div class="content-main">
<img
v-if="
(message.type === TYPES.MSG_IMAGE || message.type === TYPES.MSG_VIDEO) &&
message.hasRiskContent
(message.type === TYPES.MSG_IMAGE ||
message.type === TYPES.MSG_VIDEO) &&
message.hasRiskContent
"
:class="['message-risk-replace', !isPC && 'message-risk-replace-h5']"
:class="[
'message-risk-replace',
!isPC && 'message-risk-replace-h5'
]"
:src="riskImageReplaceUrl"
>
/>
<template v-else>
<slot />
</template>
@@ -67,10 +76,7 @@
</div>
</div>
<!-- audio unplay mark -->
<div
v-if="isDisplayUnplayMark"
class="audio-unplay-mark"
/>
<div v-if="isDisplayUnplayMark" class="audio-unplay-mark" />
<!-- Fail Icon -->
<div
v-if="message.status === 'fail' || message.hasRiskContent"
@@ -81,7 +87,10 @@
</div>
<!-- Loading Icon -->
<Icon
v-if="message.status === 'unSend' && needLoadingIconMessageType.includes(message.type)"
v-if="
message.status === 'unSend' &&
needLoadingIconMessageType.includes(message.type)
"
class="message-label loading-circle"
:file="loadingIcon"
:width="'15px'"
@@ -122,353 +131,157 @@
</template>
<script lang="ts" setup>
import { computed, toRefs } from '../../../../adapter-vue';
import TUIChatEngine, { TUITranslateService, IMessageModel } from '@tencentcloud/chat-uikit-engine-lite';
import Icon from '../../../common/Icon.vue';
import ReadStatus from './read-status/index.vue';
import MessageQuote from './message-quote/index.vue';
import Avatar from '../../../common/Avatar/index.vue';
import MessageTranslate from './message-translate/index.vue';
import MessageConvert from './message-convert/index.vue';
import RadioSelect from '../../../common/RadioSelect/index.vue';
import loadingIcon from '../../../../assets/icon/loading.png';
import { shallowCopyMessage } from '../../utils/utils';
import { isPC } from '../../../../utils/env';
import { computed, toRefs } from '../../../../adapter-vue'
import TUIChatEngine, {
TUITranslateService,
IMessageModel
} from '@tencentcloud/chat-uikit-engine-lite'
import Icon from '../../../common/Icon.vue'
import ReadStatus from './read-status/index.vue'
import MessageQuote from './message-quote/index.vue'
import Avatar from '../../../common/Avatar/index.vue'
import MessageTranslate from './message-translate/index.vue'
import MessageConvert from './message-convert/index.vue'
import RadioSelect from '../../../common/RadioSelect/index.vue'
import loadingIcon from '../../../../assets/icon/loading.png'
import { shallowCopyMessage } from '../../utils/utils'
import { isPC } from '../../../../utils/env'
interface IProps {
messageItem: IMessageModel;
content?: any;
classNameList?: string[];
blinkMessageIDList?: string[];
isMultipleSelectMode?: boolean;
isAudioPlayed?: boolean | undefined;
multipleSelectedMessageIDList?: string[];
}
interface IEmits {
(e: 'resendMessage'): void;
(e: 'blinkMessage', messageID: string): void;
(e: 'setReadReceiptPanelVisible', visible: boolean, message?: IMessageModel): void;
(e: 'changeSelectMessageIDList', options: { type: 'add' | 'remove' | 'clearAll'; messageID: string }): void;
// Only for uni-app
(e: 'scrollTo', scrollHeight: number): void;
}
const emits = defineEmits<IEmits>();
const props = withDefaults(defineProps<IProps>(), {
isAudioPlayed: false,
messageItem: () => ({} as IMessageModel),
content: () => ({}),
blinkMessageIDList: () => [],
classNameList: () => [],
isMultipleSelectMode: false,
multipleSelectedMessageIDList: () => [],
});
const TYPES = TUIChatEngine.TYPES;
const riskImageReplaceUrl = 'https://web.sdk.qcloud.com/component/TUIKit/assets/has_risk_default.png';
const needLoadingIconMessageType = [
TYPES.MSG_LOCATION,
TYPES.MSG_TEXT,
TYPES.MSG_CUSTOM,
TYPES.MSG_MERGER,
TYPES.MSG_FACE,
];
const { blinkMessageIDList, messageItem: message } = toRefs(props);
const isMultipleSelected = computed<boolean>(() => {
return props.multipleSelectedMessageIDList.includes(message.value.ID);
});
const isDisplayUnplayMark = computed<boolean>(() => {
return message.value.flow === 'in'
&& message.value.status === 'success'
&& message.value.type === TYPES.MSG_AUDIO
&& !props.isAudioPlayed;
});
const containerClassNameList = computed(() => {
return [
'message-bubble',
isMultipleSelected.value ? 'multiple-selected' : '',
...props.classNameList,
];
});
const isNoPadding = computed(() => {
return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO, TYPES.MSG_MERGER].includes(message.value.type);
});
const riskContentText = computed<string>(() => {
let content = TUITranslateService.t('TUIChat.涉及敏感内容') + ', ';
if (message.value.flow === 'out') {
content += TUITranslateService.t('TUIChat.发送失败');
} else {
content += TUITranslateService.t(
message.value.type === TYPES.MSG_AUDIO ? 'TUIChat.无法收听' : 'TUIChat.无法查看',
);
interface IProps {
messageItem: IMessageModel
content?: any
classNameList?: string[]
blinkMessageIDList?: string[]
isMultipleSelectMode?: boolean
isAudioPlayed?: boolean | undefined
multipleSelectedMessageIDList?: string[]
}
return content;
});
const isBlink = computed(() => {
if (message.value?.ID) {
return blinkMessageIDList?.value?.includes(message.value.ID);
interface IEmits {
(e: 'resendMessage'): void
(e: 'blinkMessage', messageID: string): void
(
e: 'setReadReceiptPanelVisible',
visible: boolean,
message?: IMessageModel
): void
(
e: 'changeSelectMessageIDList',
options: { type: 'add' | 'remove' | 'clearAll'; messageID: string }
): void
// Only for uni-app
(e: 'scrollTo', scrollHeight: number): void
}
return false;
});
function toggleMultipleSelect(isSelected: boolean) {
emits('changeSelectMessageIDList', {
type: isSelected ? 'add' : 'remove',
messageID: message.value.ID,
});
}
const emits = defineEmits<IEmits>()
function resendMessage() {
if (!message.value?.hasRiskContent) {
emits('resendMessage');
const props = withDefaults(defineProps<IProps>(), {
isAudioPlayed: false,
messageItem: () => ({} as IMessageModel),
content: () => ({}),
blinkMessageIDList: () => [],
classNameList: () => [],
isMultipleSelectMode: false,
multipleSelectedMessageIDList: () => []
})
const TYPES = TUIChatEngine.TYPES
const riskImageReplaceUrl =
'https://web.sdk.qcloud.com/component/TUIKit/assets/has_risk_default.png'
const needLoadingIconMessageType = [
TYPES.MSG_LOCATION,
TYPES.MSG_TEXT,
TYPES.MSG_CUSTOM,
TYPES.MSG_MERGER,
TYPES.MSG_FACE
]
const { blinkMessageIDList, messageItem: message } = toRefs(props)
const isMultipleSelected = computed<boolean>(() => {
return props.multipleSelectedMessageIDList.includes(message.value.ID)
})
const isDisplayUnplayMark = computed<boolean>(() => {
return (
message.value.flow === 'in' &&
message.value.status === 'success' &&
message.value.type === TYPES.MSG_AUDIO &&
!props.isAudioPlayed
)
})
const containerClassNameList = computed(() => {
return [
'message-bubble',
isMultipleSelected.value ? 'multiple-selected' : '',
...props.classNameList
]
})
const isNoPadding = computed(() => {
return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO, TYPES.MSG_MERGER].includes(
message.value.type
)
})
const riskContentText = computed<string>(() => {
let content = TUITranslateService.t('TUIChat.涉及敏感内容') + ', '
if (message.value.flow === 'out') {
content += TUITranslateService.t('TUIChat.发送失败')
} else {
content += TUITranslateService.t(
message.value.type === TYPES.MSG_AUDIO
? 'TUIChat.无法收听'
: 'TUIChat.无法查看'
)
}
return content
})
const isBlink = computed(() => {
if (message.value?.ID) {
return blinkMessageIDList?.value?.includes(message.value.ID)
}
return false
})
function toggleMultipleSelect(isSelected: boolean) {
emits('changeSelectMessageIDList', {
type: isSelected ? 'add' : 'remove',
messageID: message.value.ID
})
}
}
function blinkMessage(messageID: string) {
emits('blinkMessage', messageID);
}
function resendMessage() {
if (!message.value?.hasRiskContent) {
emits('resendMessage')
}
}
function scrollTo(scrollHeight: number) {
emits('scrollTo', scrollHeight);
}
function blinkMessage(messageID: string) {
emits('blinkMessage', messageID)
}
function openReadUserPanel() {
emits('setReadReceiptPanelVisible', true, message.value);
}
function scrollTo(scrollHeight: number) {
emits('scrollTo', scrollHeight)
}
function openReadUserPanel() {
emits('setReadReceiptPanelVisible', true, message.value)
}
</script>
<style lang="scss" scoped>
:not(not) {
display: flex;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
}
.flex-row {
display: flex;
}
.reverse {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
}
.message-bubble {
padding: 10px 15px;
display: flex;
flex-direction: row;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
&.multiple-selected {
background-color: #f0f0f0;
}
.multiple-select-radio {
margin-right: 12px;
flex: 0 0 auto;
}
.control-reverse {
flex: 1 1 auto;
flex-direction: row-reverse;
}
.message-bubble-main-content {
:not(not) {
display: flex;
flex-direction: row;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
}
.message-avatar {
display: block;
width: 36px;
height: 36px;
border-radius: 5px;
flex: 0 0 auto;
}
.message-body {
display: flex;
flex: 0 1 auto;
flex-direction: column;
align-items: flex-start;
margin: 0 8px;
.message-body-nick-name {
display: block;
margin-bottom: 4px;
font-size: 12px;
color: #999;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-body-main {
max-width: 100%;
display: flex;
flex-direction: row;
min-width: 0;
box-sizing: border-box;
&-reverse {
flex-direction: row-reverse;
}
.audio-unplay-mark {
flex: 0 0 auto;
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #f00;
margin: 5px;
}
.message-body-content {
display: flex;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
padding: 12px;
font-size: 14px;
color: #000;
letter-spacing: 0;
word-wrap: break-word;
word-break: break-all;
position: relative;
.content-main {
box-sizing: border-box;
display: flex;
flex-direction: column;
flex-shrink: 0;
align-content: flex-start;
border: 0 solid black;
margin: 0;
padding: 0;
min-width: 0;
.message-risk-replace {
width: 130px;
height: 130px;
}
}
.content-has-risk-tips {
font-size: 12px;
color: #fa5151;
font-family: PingFangSC-Regular;
margin-top: 5px;
border-top: 1px solid #e5c7c7;
padding-top: 5px;
}
}
.content-in {
background: #fbfbfb;
border-radius: 0 10px 10px;
}
.content-out {
background: #dceafd;
border-radius: 10px 0 10px 10px;
}
.content-no-padding {
padding: 0;
background: transparent;
border-radius: 10px;
overflow: hidden;
}
.content-no-padding.content-has-risk {
padding: 12px;
}
.content-has-risk {
background: rgba(250, 81, 81, 0.16);
}
.blink-shadow {
@keyframes shadow-blink {
50% {
box-shadow: rgba(255, 156, 25, 1) 0 0 10px 0;
}
}
box-shadow: rgba(255, 156, 25, 0) 0 0 10px 0;
animation: shadow-blink 1s linear 3;
}
.blink-content {
@keyframes reference-blink {
50% {
background-color: #ff9c19;
}
}
animation: reference-blink 1s linear 3;
}
.message-label {
align-self: flex-end;
font-family: PingFangSC-Regular;
font-size: 12px;
color: #b6b8ba;
word-break: keep-all;
flex: 0 0 auto;
margin: 0 8px;
&.fail {
width: 15px;
height: 15px;
border-radius: 15px;
background: red;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
&.loading-circle {
opacity: 0;
animation: circle-loading 2s linear 1s infinite;
}
@keyframes circle-loading {
0% {
transform: rotate(0);
opacity: 1;
}
100% {
opacity: 1;
transform: rotate(360deg);
}
}
}
.align-self-bottom {
align-self: flex-end;
}
}
}
.flex-row {
display: flex;
}
.reverse {
@@ -477,9 +290,222 @@ function openReadUserPanel() {
justify-content: flex-start;
}
.message-bubble-extra-content {
.message-bubble {
padding: 10px 15px;
display: flex;
flex-direction: column;
flex-direction: row;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
&.multiple-selected {
background-color: #f0f0f0;
}
.multiple-select-radio {
margin-right: 12px;
flex: 0 0 auto;
}
.control-reverse {
flex: 1 1 auto;
flex-direction: row-reverse;
}
.message-bubble-main-content {
display: flex;
flex-direction: row;
.message-avatar {
display: block;
width: 36px;
height: 36px;
border-radius: 5px;
flex: 0 0 auto;
}
.message-body {
display: flex;
flex: 0 1 auto;
flex-direction: column;
align-items: flex-start;
margin: 0 8px;
.message-body-nick-name {
display: block;
margin-bottom: 4px;
font-size: 12px;
color: #999;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-body-main {
max-width: 100%;
display: flex;
flex-direction: row;
min-width: 0;
box-sizing: border-box;
&-reverse {
flex-direction: row-reverse;
}
.audio-unplay-mark {
flex: 0 0 auto;
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #f00;
margin: 5px;
}
.message-body-content {
display: flex;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
padding: 16rpx 20rpx;
font-size: 14px;
color: #333;
letter-spacing: 0;
word-wrap: break-word;
word-break: break-all;
position: relative;
.content-main {
box-sizing: border-box;
display: flex;
flex-direction: column;
flex-shrink: 0;
align-content: flex-start;
border: 0 solid black;
margin: 0;
padding: 0;
min-width: 0;
.message-risk-replace {
width: 130px;
height: 130px;
}
}
.content-has-risk-tips {
font-size: 12px;
color: #fa5151;
font-family: PingFangSC-Regular;
margin-top: 5px;
border-top: 1px solid #e5c7c7;
padding-top: 5px;
}
}
.content-in {
background: #f4f4f4;
border-radius: 0 10px 10px;
}
.content-out {
background: #00D9C5;
border-radius: 10px 0 10px 10px;
}
.content-no-padding {
padding: 0;
background: transparent;
border-radius: 10px;
overflow: hidden;
}
.content-no-padding.content-has-risk {
padding: 12px;
}
.content-has-risk {
background: rgba(250, 81, 81, 0.16);
}
.blink-shadow {
@keyframes shadow-blink {
50% {
box-shadow: rgba(255, 156, 25, 1) 0 0 10px 0;
}
}
box-shadow: rgba(255, 156, 25, 0) 0 0 10px 0;
animation: shadow-blink 1s linear 3;
}
.blink-content {
@keyframes reference-blink {
50% {
background-color: #ff9c19;
}
}
animation: reference-blink 1s linear 3;
}
.message-label {
align-self: flex-end;
font-family: PingFangSC-Regular;
font-size: 12px;
color: #b6b8ba;
word-break: keep-all;
flex: 0 0 auto;
margin: 0 8px;
&.fail {
width: 15px;
height: 15px;
border-radius: 15px;
background: red;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
&.loading-circle {
opacity: 0;
animation: circle-loading 2s linear 1s infinite;
}
@keyframes circle-loading {
0% {
transform: rotate(0);
opacity: 1;
}
100% {
opacity: 1;
transform: rotate(360deg);
}
}
}
.align-self-bottom {
align-self: flex-end;
}
}
}
}
.reverse {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
}
.message-bubble-extra-content {
display: flex;
flex-direction: column;
}
}
}
</style>

View File

@@ -8,4 +8,12 @@
flex-direction: column;
box-sizing: border-box;
min-width: 0;
}
.tui-message-list {
background: #fff;
}
.message-li {
margin-top: 0 !important;
}

View File

@@ -89,7 +89,12 @@ export const sendMessages = async (
options.payload.atUserList = content.payload.atUserList;
await TUIChatService.sendTextAtMessage(options, sendMessageOptions);
} else {
await TUIChatService.sendTextMessage(options, sendMessageOptions);
try {
await TUIChatService.sendTextMessage(options, sendMessageOptions);
} catch (err) {
console.log('发送失败,对方不是你的好友')
}
}
break;
case 'image':