发送红包接口有问题,添加群恢复

This commit is contained in:
bobobobo
2026-01-08 01:44:39 +08:00
parent 1634425c17
commit c0619ea4ec
20 changed files with 2070 additions and 1531 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -72,6 +72,22 @@
</a>
</div>
</template>
<template
v-else-if="
customData.businessID === CHAT_MSG_CUSTOM_TYPE.RED_ENVELOPE
"
>
<!-- 红包 -->
<view class="red-envelope">
<view class="top-title">
<Icon :file="unopenedEnvelope" width="78rpx" height="80rpx" />
<text class="title">
{{ customData.title }}
</text>
</view>
<text class="bottom-text">积分红包</text>
</view>
</template>
<template v-else>
<span v-html="content.custom" />
</template>
@@ -89,6 +105,8 @@
import { ICustomMessagePayload } from '../../../../interface'
import Icon from '../../../common/Icon.vue'
import star from '../../../../assets/icon/star-light.png'
import unopenedEnvelope from '../../../../assets/icon/unopened-envelope.svg'
interface Props {
messageItem: IMessageModel
content: any
@@ -187,4 +205,32 @@
}
}
}
.red-envelope {
display: flex;
flex-direction: column;
background: #f3901f;
border-radius: 16rpx;
padding: 20rpx;
.top-title {
display: flex;
align-items: center;
padding-bottom: 10rpx;
margin-bottom: 10rpx;
border-bottom: 2rpx solid #ffffff;
.title {
width: 40vw;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 32rpx;
font-weight: 500;
color: #ffffff;
}
}
.bottom-text {
font-size: 24rpx;
color: #ffffff;
}
}
</style>

View File

@@ -1,8 +1,5 @@
<template>
<div
class="image-container"
@click="handleImagePreview"
>
<div class="image-container" @click="handleImagePreview">
<image
class="message-image"
mode="aspectFit"
@@ -14,72 +11,72 @@
</template>
<script lang="ts" setup>
import { watchEffect, ref } from '../../../../adapter-vue';
import type { IMessageModel } from '@tencentcloud/chat-uikit-engine-lite';
import type { IImageMessageContent } from '../../../../interface';
import { watchEffect, ref } from '../../../../adapter-vue'
import type { IMessageModel } from '@tencentcloud/chat-uikit-engine-lite'
import type { IImageMessageContent } from '../../../../interface'
interface IProps {
content: IImageMessageContent;
messageItem: IMessageModel;
}
interface IEmit {
(key: 'previewImage'): void;
}
interface IProps {
content: IImageMessageContent
messageItem: IMessageModel
}
interface IEmit {
(key: 'previewImage'): void
}
const emits = defineEmits<IEmit>();
const props = withDefaults(
defineProps<IProps>(),
{
const emits = defineEmits<IEmit>()
const props = withDefaults(defineProps<IProps>(), {
content: () => ({}),
messageItem: () => ({} as IMessageModel),
},
);
messageItem: () => ({} as IMessageModel)
})
const DEFAULT_MAX_SIZE = 155;
const imageStyles = ref({ width: 'auto', height: 'auto' });
const DEFAULT_MAX_SIZE = 155
const imageStyles = ref({ width: 'auto', height: 'auto' })
const genImageStyles = (value: { width?: any; height?: any }) => {
const { width, height } = value;
if (width === 0 || height === 0) {
return;
const genImageStyles = (value: { width?: any; height?: any }) => {
const { width, height } = value
if (width === 0 || height === 0) {
return
}
let imageWidth = 0
let imageHeight = 0
if (width >= height) {
imageWidth = DEFAULT_MAX_SIZE
imageHeight = (DEFAULT_MAX_SIZE * height) / width
} else {
imageWidth = (DEFAULT_MAX_SIZE * width) / height
imageHeight = DEFAULT_MAX_SIZE
}
imageStyles.value.width = imageWidth + 'px'
imageStyles.value.height = imageHeight + 'px'
}
let imageWidth = 0;
let imageHeight = 0;
if (width >= height) {
imageWidth = DEFAULT_MAX_SIZE;
imageHeight = (DEFAULT_MAX_SIZE * height) / width;
} else {
imageWidth = (DEFAULT_MAX_SIZE * width) / height;
imageHeight = DEFAULT_MAX_SIZE;
watchEffect(() => {
genImageStyles(props.content)
})
const imageLoad = (event: Event) => {
genImageStyles(event.detail)
}
imageStyles.value.width = imageWidth + 'px';
imageStyles.value.height = imageHeight + 'px';
};
watchEffect(() => {
genImageStyles(props.content);
});
const imageLoad = (event: Event) => {
genImageStyles(event.detail);
};
const handleImagePreview = () => {
if (props.messageItem?.status === 'success' || props.messageItem.progress === 1) {
emits('previewImage');
const handleImagePreview = () => {
if (
props.messageItem?.status === 'success' ||
props.messageItem.progress === 1
) {
emits('previewImage')
}
}
};
</script>
<style lang="scss" scoped>
.image-container {
position: relative;
background-color: #f4f4f4;
font-size: 0;
.image-container {
position: relative;
background-color: #f4f4f4;
font-size: 0;
.message-image {
max-width: 150px;
.message-image {
max-width: 150px;
}
}
}
</style>

View File

@@ -1,21 +1,20 @@
<template>
<div>
<div
class="message-record-container"
@click="openMergeDetail"
>
<div
class="record-title"
>
<div class="message-record-container" @click="openMergeDetail">
<div class="record-title">
{{ props.renderData.title }}
</div>
<div class="record-abstract-container">
<div
v-for="(item, index) in props.renderData.abstractList.slice(0, 7)"
v-for="(item, index) in props.renderData.abstractList.slice(
0,
7
)"
:key="index"
class="record-abstract-item"
>
{{ transformTextWithKeysToEmojiNames(item) }}
<!-- {{ transformTextWithKeysToEmojiNames(item) }} -->
{{ topName(item) }}
</div>
</div>
<div class="record-footer">
@@ -52,86 +51,98 @@
</template>
<script lang="ts" setup>
import { ref, withDefaults } from '../../../../../adapter-vue';
import { TUITranslateService, IMessageModel } from '@tencentcloud/chat-uikit-engine-lite';
import Overlay from '../../../../common/Overlay/index.vue';
import Drawer from '../../../../common/Drawer/index.vue';
import SimpleMessageList from '../simple-message-list/index.vue';
import { isH5, isPC, isUniFrameWork } from '../../../../../utils/env';
import { transformTextWithKeysToEmojiNames } from '../../../emoji-config/index';
import { IMergeMessageContent } from '../../../../../interface';
import { ref, computed, withDefaults } from '../../../../../adapter-vue'
import {
TUITranslateService,
IMessageModel
} from '@tencentcloud/chat-uikit-engine-lite'
import Overlay from '../../../../common/Overlay/index.vue'
import Drawer from '../../../../common/Drawer/index.vue'
import SimpleMessageList from '../simple-message-list/index.vue'
import { isH5, isPC, isUniFrameWork } from '../../../../../utils/env'
import { transformTextWithKeysToEmojiNames } from '../../../emoji-config/index'
import { IMergeMessageContent } from '../../../../../interface'
import { CHAT_MSG_CUSTOM_TYPE } from '../../../../../constant'
interface IEmits {
(e: 'assignMessageIDInUniapp', messageID: string): void;
}
interface IProps {
// Core data for rendering message record card and message list
renderData: IMergeMessageContent;
/**
* The MessageRecord component has two main functions:
* 1. display message record cards primarily.
* 2. clicking on it and show the simple message list.
* When used as a nested component with the disabled prop
* it is only need renderData to render message record cards.
* Therefore, 'messageItem' and 'disabled' is not a required prop.
*/
disabled?: boolean;
messageItem?: IMessageModel;
}
const emits = defineEmits<IEmits>();
const props = withDefaults(defineProps<IProps>(), {
messageItem: () => ({}) as IMessageModel,
disabled: false,
});
const isMessageListVisible = ref(false);
function openMergeDetail() {
if (props.disabled) {
return;
interface IEmits {
(e: 'assignMessageIDInUniapp', messageID: string): void
}
if (!isUniFrameWork) {
isMessageListVisible.value = true;
} else {
emits('assignMessageIDInUniapp', props.messageItem.ID);
}
}
function closeMergeDetail() {
isMessageListVisible.value = false;
}
interface IProps {
// Core data for rendering message record card and message list
renderData: IMergeMessageContent
/**
* The MessageRecord component has two main functions:
* 1. display message record cards primarily.
* 2. clicking on it and show the simple message list.
* When used as a nested component with the disabled prop
* it is only need renderData to render message record cards.
* Therefore, 'messageItem' and 'disabled' is not a required prop.
*/
disabled?: boolean
messageItem?: IMessageModel
}
const emits = defineEmits<IEmits>()
const props = withDefaults(defineProps<IProps>(), {
messageItem: () => ({} as IMessageModel),
disabled: false
})
const isMessageListVisible = ref(false)
// 把替[自定义消息]替换为积分红包
const topName = (str: string) => {
if (str.includes('[自定义消息]')) {
return str.replace('[自定义消息]', '[积分红包]')
} else {
return transformTextWithKeysToEmojiNames(str)
}
}
function openMergeDetail() {
if (props.disabled) {
return
}
if (!isUniFrameWork) {
isMessageListVisible.value = true
} else {
emits('assignMessageIDInUniapp', props.messageItem.ID)
}
}
function closeMergeDetail() {
isMessageListVisible.value = false
}
</script>
<style lang="scss" scoped>
:not(not) {
display: flex;
flex-direction: column;
box-sizing: border-box;
min-width: 0;
}
.message-record-container {
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 10px;
cursor: pointer;
background-color: #fff;
max-width: 400px;
min-width: 180px;
overflow: hidden;
.record-abstract-container {
color: #bbb;
font-size: 12px;
margin: 8px 0;
:not(not) {
display: flex;
flex-direction: column;
box-sizing: border-box;
min-width: 0;
}
.record-footer {
color: #888;
font-size: 11px;
padding-top: 5px;
border-top: 1px solid #eee;
.message-record-container {
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 10px;
cursor: pointer;
background-color: #fff;
max-width: 400px;
min-width: 180px;
overflow: hidden;
.record-abstract-container {
color: #bbb;
font-size: 12px;
margin: 8px 0;
}
.record-footer {
color: #888;
font-size: 11px;
padding-top: 5px;
border-top: 1px solid #eee;
}
}
}
</style>

View File

@@ -2,20 +2,15 @@
<div
:class="{
'simple-message-list-container': true,
'simple-message-list-container-mobile': isMobile,
'simple-message-list-container-mobile': isMobile
}"
>
<div class="header-container">
<span
class="back"
@click="backPreviousLevel"
>
<Icon
class="close-icon"
:file="addIcon"
:size="'18px'"
/>
<span v-if="isReturn">{{ TUITranslateService.t('TUIChat.返回') }}</span>
<span class="back" @click="backPreviousLevel">
<Icon class="close-icon" :file="addIcon" :size="'18px'" />
<span v-if="isReturn">
{{ TUITranslateService.t('TUIChat.返回') }}
</span>
<span v-else>{{ TUITranslateService.t('TUIChat.关闭') }}</span>
</span>
@@ -23,9 +18,7 @@
{{ currentMergeMessageInfo.title }}
</span>
</div>
<div v-if="isDownloadOccurError">
Load Merge Message Error
</div>
<div v-if="isDownloadOccurError">Load Merge Message Error</div>
<div
v-else-if="isMergeMessageInfoLoaded"
ref="simpleMessageListRef"
@@ -35,7 +28,7 @@
v-for="item in currentMergeMessageInfo.messageList"
:key="item.ID"
:class="{
'message-item': true,
'message-item': true
}"
>
<MessageContainer
@@ -50,20 +43,21 @@
class="message-text"
>
<span
v-for="(textInfo, index) in parseTextToRenderArray(item.messageBody[0].payload['text'])"
v-for="(textInfo, index) in parseTextToRenderArray(
item.messageBody[0].payload['text']
)"
:key="index"
class="message-text-container"
>
<span
v-if="textInfo.type === 'text'"
class="text"
>{{ textInfo.content }}</span>
<span v-if="textInfo.type === 'text'" class="text">
{{ textInfo.content }}
</span>
<img
v-else
class="simple-emoji"
:src="textInfo.content"
alt="small-face"
>
/>
</span>
</div>
<!-- image -->
@@ -73,10 +67,12 @@
>
<img
class="image"
:src="(item.messageBody[0].payload)['imageInfoArray'][2]['url']"
:src="
item.messageBody[0].payload['imageInfoArray'][2]['url']
"
mode="widthFix"
alt="image"
>
/>
</div>
<!-- video -->
<div
@@ -85,29 +81,30 @@
>
<div
v-if="isUniFrameWork"
@click="previewVideoInUniapp((item.messageBody[0].payload)['remoteVideoUrl'])"
@click="
previewVideoInUniapp(
item.messageBody[0].payload['remoteVideoUrl']
)
"
>
<image
class="image"
:src="(item.messageBody[0].payload)['thumbUrl']"
:src="item.messageBody[0].payload['thumbUrl']"
mode="widthFix"
alt="image"
/>
<Icon
class="video-play-icon"
:file="playIcon"
/>
<Icon class="video-play-icon" :file="playIcon" />
</div>
<video
v-else
class="video"
controls
:poster="(item.messageBody[0].payload)['thumbUrl']"
:poster="item.messageBody[0].payload['thumbUrl']"
>
<source
:src="(item.messageBody[0].payload)['remoteVideoUrl']"
:src="item.messageBody[0].payload['remoteVideoUrl']"
type="video/mp4"
>
/>
</video>
</div>
<!-- audio -->
@@ -115,7 +112,7 @@
v-else-if="item.messageBody[0].type === TYPES.MSG_AUDIO"
class="message-audio"
>
<span>{{ TUITranslateService.t("TUIChat.语音") }}&nbsp;</span>
<span>{{ TUITranslateService.t('TUIChat.语音') }}&nbsp;</span>
<span>{{ item.messageBody[0].payload.second }}s</span>
</div>
<!-- big face -->
@@ -127,7 +124,7 @@
class="image"
:src="resolveBigFaceUrl(item.messageBody[0].payload.data)"
alt="face"
>
/>
</div>
<!-- file -->
<div
@@ -153,6 +150,10 @@
:renderData="item.messageBody[0].payload"
/>
</div>
<!-- 红包 -->
<div v-else-if="isRedEnvelope(item)">
{{ redEnvelopeText(item) }}
</div>
<!-- custom -->
<div v-else-if="item.messageBody[0].type === TYPES.MSG_CUSTOM">
{{ TUITranslateService.t('TUIChat.[自定义消息]') }}
@@ -164,270 +165,309 @@
</template>
<script setup lang="ts">
import { computed, ref, watch } from '../../../../../adapter-vue';
import TUIChatEngine, {
TUIStore,
TUIChatService,
TUITranslateService,
} from '@tencentcloud/chat-uikit-engine-lite';
import addIcon from '../../../../../assets/icon/back.svg';
import playIcon from '../../../../../assets/icon/video-play.png';
import Icon from '../../../../common/Icon.vue';
import MessageContainer from './message-container.vue';
import MessageRecord from '../message-record/index.vue';
import { parseTextToRenderArray, DEFAULT_BIG_EMOJI_URL, CUSTOM_BIG_EMOJI_URL } from '../../../emoji-config/index';
import { isMobile, isUniFrameWork } from '../../../../../utils/env';
import { IMergeMessageContent } from '../../../../../interface';
import { computed, ref, watch } from '../../../../../adapter-vue'
import TUIChatEngine, {
TUIStore,
TUIChatService,
TUITranslateService
} from '@tencentcloud/chat-uikit-engine-lite'
import addIcon from '../../../../../assets/icon/back.svg'
import playIcon from '../../../../../assets/icon/video-play.png'
import Icon from '../../../../common/Icon.vue'
import MessageContainer from './message-container.vue'
import MessageRecord from '../message-record/index.vue'
import {
parseTextToRenderArray,
DEFAULT_BIG_EMOJI_URL,
CUSTOM_BIG_EMOJI_URL
} from '../../../emoji-config/index'
import { isMobile, isUniFrameWork } from '../../../../../utils/env'
import { IMergeMessageContent } from '../../../../../interface'
import { CHAT_MSG_CUSTOM_TYPE } from '../../../../../constant'
interface IProps {
/**
* only use messageID when first render of simple-message-list
* because the nested simple-message-list do not have corresponding message object
* need to download message from sdk by constructed message
* and use downloaded message object to render nested simple-message-list
*/
messageID?: string;
isMounted?: boolean;
}
interface IEmits {
(e: 'closeOverlay'): void;
}
const emits = defineEmits<IEmits>();
const props = withDefaults(defineProps<IProps>(), {
messageID: '',
isMounted: false,
});
const TYPES = TUIChatEngine.TYPES;
const isDownloadOccurError = ref(false);
const messageListStack = ref<IMergeMessageContent[]>([]);
const currentMergeMessageInfo = ref<Partial<IMergeMessageContent>>({
title: '',
messageList: [],
});
const simpleMessageListRef = ref<HTMLElement>();
watch(() => messageListStack.value.length, async (newValue) => {
isDownloadOccurError.value = false;
if (newValue < 1) {
return;
interface IProps {
/**
* only use messageID when first render of simple-message-list
* because the nested simple-message-list do not have corresponding message object
* need to download message from sdk by constructed message
* and use downloaded message object to render nested simple-message-list
*/
messageID?: string
isMounted?: boolean
}
const stackTopMessageInfo = messageListStack.value[messageListStack.value.length - 1];
if (stackTopMessageInfo.downloadKey && stackTopMessageInfo.messageList.length === 0) {
try {
const res = await TUIChatService.downloadMergedMessages({
payload: stackTopMessageInfo,
type: TUIChatEngine.TYPES.MSG_MERGER,
} as any);
// if download complete message, cover the original message in stack top
messageListStack.value[messageListStack.value.length - 1] = res.payload;
} catch (error) {
isDownloadOccurError.value = true;
interface IEmits {
(e: 'closeOverlay'): void
}
const emits = defineEmits<IEmits>()
const props = withDefaults(defineProps<IProps>(), {
messageID: '',
isMounted: false
})
const TYPES = TUIChatEngine.TYPES
const isDownloadOccurError = ref(false)
const messageListStack = ref<IMergeMessageContent[]>([])
const currentMergeMessageInfo = ref<Partial<IMergeMessageContent>>({
title: '',
messageList: []
})
const simpleMessageListRef = ref<HTMLElement>()
watch(
() => messageListStack.value.length,
async newValue => {
isDownloadOccurError.value = false
if (newValue < 1) {
return
}
const stackTopMessageInfo =
messageListStack.value[messageListStack.value.length - 1]
if (
stackTopMessageInfo.downloadKey &&
stackTopMessageInfo.messageList.length === 0
) {
try {
const res = await TUIChatService.downloadMergedMessages({
payload: stackTopMessageInfo,
type: TUIChatEngine.TYPES.MSG_MERGER
} as any)
// if download complete message, cover the original message in stack top
messageListStack.value[messageListStack.value.length - 1] =
res.payload
} catch (error) {
isDownloadOccurError.value = true
}
}
currentMergeMessageInfo.value =
messageListStack.value[messageListStack.value.length - 1]
}
)
watch(
() => props.isMounted,
newValue => {
// For compatibility with uniapp, use watch to implement onMounted
if (newValue) {
if (!props.messageID) {
throw new Error(
'messageID is required when first render of simple-message-list.'
)
}
const sdkMessagePayload = TUIStore.getMessageModel(
props.messageID
).getMessage().payload
messageListStack.value = [sdkMessagePayload]
} else {
messageListStack.value = []
}
},
{
immediate: true
}
)
const isReturn = computed(() => {
return messageListStack.value.length > 1
})
const isMergeMessageInfoLoaded = computed(() => {
return currentMergeMessageInfo.value?.messageList
? currentMergeMessageInfo.value.messageList.length > 0
: false
})
function entryNextLevel(e, sdkMessage: any) {
messageListStack.value.push(sdkMessage.messageBody[0].payload)
e.stopPropagation()
}
function backPreviousLevel() {
messageListStack.value.pop()
if (messageListStack.value.length < 1) {
emits('closeOverlay')
}
}
currentMergeMessageInfo.value = messageListStack.value[messageListStack.value.length - 1];
});
watch(() => props.isMounted, (newValue) => {
// For compatibility with uniapp, use watch to implement onMounted
if (newValue) {
if (!props.messageID) {
throw new Error('messageID is required when first render of simple-message-list.');
function previewVideoInUniapp(url: string) {
if (isUniFrameWork) {
const encodedUrl = encodeURIComponent(url)
uni.navigateTo({
url: `/TUIKit/components/TUIChat/video-play?videoUrl=${encodedUrl}`
})
}
const sdkMessagePayload = TUIStore.getMessageModel(props.messageID).getMessage().payload;
messageListStack.value = [sdkMessagePayload];
} else {
messageListStack.value = [];
}
}, {
immediate: true,
});
const isReturn = computed(() => {
return messageListStack.value.length > 1;
});
const isMergeMessageInfoLoaded = computed(() => {
return currentMergeMessageInfo.value?.messageList ? currentMergeMessageInfo.value.messageList.length > 0 : false;
});
function entryNextLevel(e, sdkMessage: any) {
messageListStack.value.push(sdkMessage.messageBody[0].payload);
e.stopPropagation();
}
function backPreviousLevel() {
messageListStack.value.pop();
if (messageListStack.value.length < 1) {
emits('closeOverlay');
}
}
function previewVideoInUniapp(url: string) {
if (isUniFrameWork) {
const encodedUrl = encodeURIComponent(url);
uni.navigateTo({
url: `/TUIKit/components/TUIChat/video-play?videoUrl=${encodedUrl}`,
});
}
}
function resolveBigFaceUrl(bigFaceKey: string): string {
let url = '';
if (bigFaceKey.indexOf('@custom') > -1) {
url = CUSTOM_BIG_EMOJI_URL + bigFaceKey;
} else {
url = DEFAULT_BIG_EMOJI_URL + bigFaceKey;
if (url.indexOf('@2x') === -1) {
url += '@2x.png';
function resolveBigFaceUrl(bigFaceKey: string): string {
let url = ''
if (bigFaceKey.indexOf('@custom') > -1) {
url = CUSTOM_BIG_EMOJI_URL + bigFaceKey
} else {
url += '.png';
url = DEFAULT_BIG_EMOJI_URL + bigFaceKey
if (url.indexOf('@2x') === -1) {
url += '@2x.png'
} else {
url += '.png'
}
}
return url
}
/** 是否红包 */
const isRedEnvelope = (item: any) => {
if (item.messageBody[0]?.payload?.data) {
const businessID = JSON?.parse(
item.messageBody[0]?.payload?.data
)?.businessID
return businessID === CHAT_MSG_CUSTOM_TYPE.RED_ENVELOPE
}
return false
}
/** 红包文案 */
const redEnvelopeText = (item: any) => {
const payload = JSON.parse(item.messageBody[0]?.payload?.data)
return `[积分红包] ${payload.title}`
}
return url;
}
</script>
<style scoped lang="scss">
:not(not){
display: flex;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
}
.simple-message-list-container {
position: relative;
overflow: hidden;
width: calc(40vw);
min-width: 550px;
height: calc(100vh - 200px);
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 8px;
&-mobile {
width: 100vw;
height: 100vh;
min-width: auto;
border-radius: 0;
:not(not) {
display: flex;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
}
.header-container {
width: 100%;
text-align: center;
font-weight: bold;
position: absolute;
top: 0;
left: 0;
z-index: 1;
height: 60px;
justify-content: center;
align-items: center;
padding: 0 70px;
.simple-message-list-container {
position: relative;
overflow: hidden;
width: calc(40vw);
min-width: 550px;
height: calc(100vh - 200px);
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 8px;
.back {
flex-direction: row;
align-items: center;
position: absolute;
left: 10px;
cursor: pointer;
&-mobile {
width: 100vw;
height: 100vh;
min-width: auto;
border-radius: 0;
}
.title {
.header-container {
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
font-weight: bold;
position: absolute;
top: 0;
left: 0;
z-index: 1;
height: 60px;
justify-content: center;
align-items: center;
padding: 0 70px;
background-color: #fff;
.back {
flex-direction: row;
align-items: center;
position: absolute;
left: 10px;
cursor: pointer;
}
.title {
width: 100%;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.message-list {
padding: 60px 20px 20px;
flex: 1 1 auto;
overflow: hidden auto;
}
}
.message-list {
padding: 60px 20px 20px;
flex: 1 1 auto;
overflow: hidden auto;
}
}
.message-item {
flex-direction: row;
margin: 10px 0;
}
.message-text {
flex-flow: row wrap;
display: inline;
&-container {
display: inline;
flex: 0 0 auto;
.message-item {
flex-direction: row;
margin: 10px 0;
}
.text {
vertical-align: bottom;
.message-text {
flex-flow: row wrap;
display: inline;
&-container {
display: inline;
word-break: break-all;
}
flex: 0 0 auto;
flex-direction: row;
.simple-emoji {
display: inline-flex;
width: 20px;
height: 20px;
.text {
vertical-align: bottom;
display: inline;
word-break: break-all;
}
.simple-emoji {
display: inline-flex;
width: 20px;
height: 20px;
}
}
}
}
.message-image {
max-width: 180px;
border-radius: 10px;
overflow: hidden;
.image {
.message-image {
max-width: 180px;
}
}
.message-face {
max-width: 100px;
.image {
width: 80px;
height: 80px;
}
}
.message-audio {
flex-direction: row;
}
.message-video {
position: relative;
.image {
max-width: 180px;
}
.video-play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.video {
max-width: 150px;
width: inherit;
height: inherit;
border-radius: 10px;
}
}
overflow: hidden;
.message-combine {
max-width: 300px;
}
.image {
max-width: 180px;
}
}
.message-face {
max-width: 100px;
.image {
width: 80px;
height: 80px;
}
}
.message-audio {
flex-direction: row;
}
.message-video {
position: relative;
.image {
max-width: 180px;
}
.video-play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.video {
max-width: 150px;
width: inherit;
height: inherit;
border-radius: 10px;
}
}
.message-combine {
max-width: 300px;
}
</style>