|
@@ -0,0 +1,635 @@
|
|
|
+<template>
|
|
|
+ <div class="message-bubble" :class="[message.flow === 'in' ? '' : 'reverse']" ref="htmlRefHook">
|
|
|
+ <img
|
|
|
+ class="avatar"
|
|
|
+ :src="message?.avatar || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
|
|
|
+ onerror="this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
|
|
|
+ />
|
|
|
+ <main class="message-area">
|
|
|
+ <label class="name" v-if="message.flow === 'in' && message.conversationType === 'GROUP'">
|
|
|
+ {{ message.nameCard || message.nick || message.from }}
|
|
|
+ </label>
|
|
|
+ <div :class="handleImageOrVideoBubbleStyle(message)" @click.prevent.right="toggleDialog">
|
|
|
+ <div
|
|
|
+ class="message-replie-area"
|
|
|
+ :class="[message?.flow === 'in' ? '' : 'message-replies-area-reverse']"
|
|
|
+ v-if="message?.cloudCustomData && referenceMessage && referenceMessage?.messageRootID"
|
|
|
+ @click="showRepliesDialog(message, false)"
|
|
|
+ >
|
|
|
+ <MessageReference
|
|
|
+ :message="message"
|
|
|
+ :referenceMessage="referenceMessage"
|
|
|
+ :referenceForShow="referenceForShow"
|
|
|
+ :url="url"
|
|
|
+ :face="face"
|
|
|
+ :allMessageID="allMessageID"
|
|
|
+ type="reply"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <slot />
|
|
|
+ <div v-if="dropdown" ref="dropdownRef" class="dropdown-inner">
|
|
|
+ <div class="dialog" :class="[message.flow === 'in' ? '' : 'dialog-right']" @click="dropdown = false">
|
|
|
+ <slot name="dialog" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <MessageEmojiReact :message="message" type="content" v-if="needEmojiReact && isEmojiReactionInMessage(message)" />
|
|
|
+ </div>
|
|
|
+ </main>
|
|
|
+ <label class="message-label fail" v-if="message.status === 'fail'" @click="resendMessage(message)">!</label>
|
|
|
+ <label
|
|
|
+ class="message-label"
|
|
|
+ :class="readReceiptStyle(message)"
|
|
|
+ v-if="showReadReceiptTag(message)"
|
|
|
+ @click="showReadReceiptDialog(message)"
|
|
|
+ >
|
|
|
+ <span>{{ readReceiptCont(message) }}</span>
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="message-reference-area"
|
|
|
+ :class="[message.flow === 'in' ? '' : 'message-reference-area-reverse']"
|
|
|
+ v-if="message?.cloudCustomData && referenceMessage && !referenceMessage?.messageRootID"
|
|
|
+ @click="jumpToAim(referenceMessage)"
|
|
|
+ >
|
|
|
+ <MessageReference
|
|
|
+ :message="message"
|
|
|
+ :referenceMessage="referenceMessage"
|
|
|
+ :referenceForShow="referenceForShow"
|
|
|
+ :url="url"
|
|
|
+ :face="face"
|
|
|
+ :allMessageID="allMessageID"
|
|
|
+ type="reference"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <label
|
|
|
+ class="message-replies"
|
|
|
+ :class="[message.flow === 'in' ? '' : 'message-replies-reverse']"
|
|
|
+ v-if="replies?.length"
|
|
|
+ @click="showRepliesDialog(message, true)"
|
|
|
+ >
|
|
|
+ <i class="icon icon-msg-replies"></i>
|
|
|
+ <span>{{ replies?.length + $t('TUIChat.条回复') }}</span>
|
|
|
+ </label>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts">
|
|
|
+import { decodeText } from '../utils/decodeText';
|
|
|
+import constant from '../../constant';
|
|
|
+import { defineComponent, watchEffect, reactive, toRefs, ref, nextTick, watch } from 'vue';
|
|
|
+import { onClickOutside, onLongPress, useElementBounding } from '@vueuse/core';
|
|
|
+import { deepCopy, JSONToObject } from '../utils/utils';
|
|
|
+import { handleErrorPrompts } from '../../utils';
|
|
|
+import TUIChat from '../index.vue';
|
|
|
+import MessageReference from './message-reference.vue';
|
|
|
+import { Message } from '../interface';
|
|
|
+import { TUIEnv } from '../../../../TUIPlugin';
|
|
|
+import MessageEmojiReact from './message-emoji-react.vue';
|
|
|
+import TIM from '../../../../TUICore/tim/index';
|
|
|
+
|
|
|
+const messageBubble = defineComponent({
|
|
|
+ props: {
|
|
|
+ data: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+ messagesList: {
|
|
|
+ type: Array,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ isH5: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ needGroupReceipt: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ needReplies: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+ flow: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ needEmojiReact: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ emits: ['jumpID', 'resendMessage', 'showReadReceiptDialog', 'showRepliesDialog', 'dropDownOpen'],
|
|
|
+ components: {
|
|
|
+ MessageReference,
|
|
|
+ MessageEmojiReact,
|
|
|
+ },
|
|
|
+ setup(props: any, ctx: any) {
|
|
|
+ const { t } = (window as any).TUIKitTUICore.config.i18n.useI18n();
|
|
|
+ const { TUIServer } = TUIChat;
|
|
|
+ const data = reactive({
|
|
|
+ env: TUIEnv(),
|
|
|
+ message: {} as Message,
|
|
|
+ messagesList: [],
|
|
|
+ show: false,
|
|
|
+ type: {},
|
|
|
+ referenceMessage: {},
|
|
|
+ referenceForShow: {},
|
|
|
+ allMessageID: '',
|
|
|
+ needGroupReceipt: false,
|
|
|
+ needReplies: true,
|
|
|
+ replies: [],
|
|
|
+ face: [],
|
|
|
+ url: '',
|
|
|
+ needEmojiReact: false,
|
|
|
+ });
|
|
|
+
|
|
|
+ watchEffect(() => {
|
|
|
+ data.type = constant;
|
|
|
+ data.messagesList = props.messagesList;
|
|
|
+ data.needEmojiReact = props.needEmojiReact;
|
|
|
+ data.message = deepCopy(
|
|
|
+ data.messagesList?.find((item: any) => (item as any)?.ID === props.message?.ID) || props.data
|
|
|
+ );
|
|
|
+ data.needGroupReceipt = props.needGroupReceipt;
|
|
|
+ data.needReplies = props.needReplies;
|
|
|
+ if ((data.message as any).cloudCustomData) {
|
|
|
+ const messageIDList: any[] = [];
|
|
|
+ const cloudCustomData = JSONToObject((data.message as any).cloudCustomData);
|
|
|
+ data.replies = cloudCustomData?.messageReplies?.replies || [];
|
|
|
+ data.referenceMessage = cloudCustomData.messageReply ? cloudCustomData.messageReply : '';
|
|
|
+ for (let index = 0; index < (data.messagesList as any).length; index++) {
|
|
|
+ // To determine whether the referenced message is still in the message list, the corresponding field of the referenced message is displayed if it is in the message list. Otherwise, messageabstract/messagesender is displayed
|
|
|
+ messageIDList.push((data.messagesList as any)[index].ID);
|
|
|
+ (data as any).allMessageID = JSON.stringify(messageIDList);
|
|
|
+ if ((data.messagesList as any)[index].ID === (data.referenceMessage as any)?.messageID) {
|
|
|
+ data.referenceForShow = (data.messagesList as any)[index];
|
|
|
+ if ((data.referenceMessage as any).messageType === constant.typeText) {
|
|
|
+ (data as any).face = decodeText((data.referenceForShow as any).payload);
|
|
|
+ }
|
|
|
+ if ((data.referenceMessage as any).messageType === constant.typeFace) {
|
|
|
+ (data as any).url = `https://web.sdk.qcloud.com/im/assets/face-elem/${
|
|
|
+ (data.referenceForShow as any).payload.data
|
|
|
+ }@2x.png`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ data.replies = [];
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const htmlRefHook = ref<HTMLElement | null>(null);
|
|
|
+ const dropdown = ref(false);
|
|
|
+ const dropdownRef = ref(null);
|
|
|
+
|
|
|
+ const toggleDialog = (e: any) => {
|
|
|
+ dropdown.value = !dropdown.value;
|
|
|
+ if (dropdown.value) {
|
|
|
+ ctx.emit('dropDownOpen', dropdownRef);
|
|
|
+ nextTick(() => {
|
|
|
+ const dialogDom = (dropdownRef as any)?.value?.children[0];
|
|
|
+ const dialogElement = document.getElementsByClassName('dialog-item')[0] as HTMLElement;
|
|
|
+ const parentDom = (dropdownRef as any)?.value?.offsetParent;
|
|
|
+ const parentBound = useElementBounding(parentDom);
|
|
|
+ const messageListDom = document.getElementById('messageEle') as HTMLElement;
|
|
|
+ const messageListBound = useElementBounding(messageListDom);
|
|
|
+ const leftRange = messageListBound?.left?.value;
|
|
|
+ const rightRange =
|
|
|
+ messageListBound?.left?.value + (messageListDom as any).clientWidth - dialogDom.clientWidth + 76;
|
|
|
+ const topRange = messageListBound?.top?.value;
|
|
|
+ const bottomRange =
|
|
|
+ messageListBound?.top?.value + (messageListDom as any).clientHeight - dialogDom.clientHeight;
|
|
|
+ const { clientX, clientY } = e;
|
|
|
+ if (data?.env?.isH5) {
|
|
|
+ if (parentBound?.top?.value <= dialogElement?.clientHeight) {
|
|
|
+ dialogDom.style.bottom = `-${dialogElement?.clientHeight}px`;
|
|
|
+ } else {
|
|
|
+ if (data?.message?.flow === 'in') {
|
|
|
+ dialogDom.style.top = `-${dialogElement?.clientHeight - 20}px`;
|
|
|
+ } else {
|
|
|
+ dialogDom.style.top = `-${dialogElement?.clientHeight}px`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const centerWidth = parentBound?.left?.value + parentBound?.width?.value / 2;
|
|
|
+ if (
|
|
|
+ centerWidth > dialogElement.clientWidth / 2 &&
|
|
|
+ centerWidth < messageListDom?.clientWidth - dialogElement.clientWidth / 2
|
|
|
+ ) {
|
|
|
+ dialogDom.style.left = 'calc(50% - 135px)';
|
|
|
+ } else if (centerWidth <= dialogElement.clientWidth / 2) {
|
|
|
+ dialogDom.style.left = '-20px';
|
|
|
+ } else {
|
|
|
+ dialogDom.style.left = `-${dialogElement.clientWidth / 2 + 10}px`;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ switch (true) {
|
|
|
+ case clientX > leftRange && clientX < rightRange:
|
|
|
+ dialogDom.style.left = `${Math.max(e.clientX - parentBound?.left?.value - 76, -40)}px`;
|
|
|
+ break;
|
|
|
+ case clientX <= leftRange:
|
|
|
+ dialogDom.style.left = '20px';
|
|
|
+ break;
|
|
|
+ case clientX >= rightRange:
|
|
|
+ dialogDom.style.right = `${Math.max(
|
|
|
+ parentBound?.left?.value + parentDom?.clientWidth - e.clientX - 256,
|
|
|
+ -10
|
|
|
+ )}px`;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ switch (true) {
|
|
|
+ case clientY > topRange && clientY < bottomRange:
|
|
|
+ dialogDom.style.top = `${e.clientY - parentBound?.top?.value}px`;
|
|
|
+ dialogDom.style.cssText = dialogDom.style.cssText.replace('align-items:end;', '');
|
|
|
+ break;
|
|
|
+ case clientY <= topRange:
|
|
|
+ dialogDom.style.top = '0px';
|
|
|
+ dialogDom.style.cssText = dialogDom.style.cssText.replace('align-items:end;', '');
|
|
|
+ break;
|
|
|
+ case clientY >= bottomRange:
|
|
|
+ dialogDom.style.bottom = `${parentBound?.top?.value + parentDom?.clientHeight - e.clientY}px`;
|
|
|
+ dialogDom.style.cssText += 'align-items:end;';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const jumpToAim = (message: any) => {
|
|
|
+ if (
|
|
|
+ (data.referenceMessage as any)?.messageID &&
|
|
|
+ data.allMessageID.includes((data.referenceMessage as any)?.messageID)
|
|
|
+ ) {
|
|
|
+ ctx.emit('jumpID', (data.referenceMessage as any).messageID);
|
|
|
+ } else {
|
|
|
+ const message = t('TUIChat.无法定位到原消息');
|
|
|
+ handleErrorPrompts(message, props);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ onClickOutside(dropdownRef, () => {
|
|
|
+ dropdown.value = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ const toggleDialogH5 = (e: any) => {
|
|
|
+ if (data?.env?.isH5) toggleDialog(e);
|
|
|
+ return;
|
|
|
+ };
|
|
|
+
|
|
|
+ onLongPress(htmlRefHook, toggleDialogH5);
|
|
|
+
|
|
|
+ const resendMessage = (message: any) => {
|
|
|
+ ctx.emit('resendMessage', message);
|
|
|
+ };
|
|
|
+
|
|
|
+ const showReadReceiptTag = (message: any) => {
|
|
|
+ if (message.flow === 'out' && message.status === 'success' && message.needReadReceipt) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const readReceiptStyle = (message: any) => {
|
|
|
+ if (
|
|
|
+ message?.readReceiptInfo?.isPeerRead ||
|
|
|
+ (message?.readReceiptInfo?.isPeerRead === undefined && message?.isPeerRead) ||
|
|
|
+ message?.readReceiptInfo?.unreadCount === 0
|
|
|
+ ) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ return 'unRead';
|
|
|
+ };
|
|
|
+
|
|
|
+ const readReceiptCont = (message: any) => {
|
|
|
+ switch (message.conversationType) {
|
|
|
+ case TUIServer.TUICore.TIM.TYPES.CONV_C2C:
|
|
|
+ if (
|
|
|
+ message?.readReceiptInfo?.isPeerRead ||
|
|
|
+ (message?.readReceiptInfo?.isPeerRead === undefined && message?.isPeerRead)
|
|
|
+ ) {
|
|
|
+ return t('TUIChat.已读');
|
|
|
+ }
|
|
|
+ return t('TUIChat.未读');
|
|
|
+ case TUIServer.TUICore.TIM.TYPES.CONV_GROUP:
|
|
|
+ if (message.readReceiptInfo.unreadCount === 0) {
|
|
|
+ return t('TUIChat.全部已读');
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ message.readReceiptInfo.readCount === 0 ||
|
|
|
+ (message.readReceiptInfo.unreadCount === undefined && message.readReceiptInfo.readCount === undefined)
|
|
|
+ ) {
|
|
|
+ return t('TUIChat.未读');
|
|
|
+ }
|
|
|
+ return `${message.readReceiptInfo.readCount + t('TUIChat.人已读')}`;
|
|
|
+ default:
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const showReadReceiptDialog = (message: any) => {
|
|
|
+ ctx.emit('showReadReceiptDialog', message, 'receipt');
|
|
|
+ };
|
|
|
+
|
|
|
+ const showRepliesDialog = (message: any, isRoot: boolean) => {
|
|
|
+ if (isRoot) {
|
|
|
+ ctx.emit('showRepliesDialog', message, 'replies');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ((data.referenceMessage as any)?.messageRootID) {
|
|
|
+ const message = data.messagesList?.find(
|
|
|
+ (item: Message) => item.ID === (data.referenceMessage as any)?.messageRootID
|
|
|
+ );
|
|
|
+ if (message) {
|
|
|
+ ctx.emit('showRepliesDialog', message, 'replies');
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ const message = t('TUIChat.无法定位到原消息');
|
|
|
+ handleErrorPrompts(message, props);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleImageOrVideoBubbleStyle = (message: Message) => {
|
|
|
+ const classNameList = ['content'];
|
|
|
+ if (!message) return classNameList;
|
|
|
+ classNameList.push(`content-${data.message.flow}`);
|
|
|
+ if (data.message.type === TIM.TYPES.MSG_IMAGE && !isEmojiReactionInMessage(message)) {
|
|
|
+ classNameList.push('content-image');
|
|
|
+ }
|
|
|
+ if (data.message.type === TIM.TYPES.MSG_VIDEO && !isEmojiReactionInMessage(message)) {
|
|
|
+ classNameList.push('content-video');
|
|
|
+ }
|
|
|
+ return classNameList;
|
|
|
+ };
|
|
|
+
|
|
|
+ const isEmojiReactionInMessage = (message: Message) => {
|
|
|
+ try {
|
|
|
+ if (!message?.cloudCustomData) return;
|
|
|
+ const reactList = JSONToObject(message?.cloudCustomData)?.messageReact?.reacts;
|
|
|
+ if (!reactList || Object.keys(reactList).length === 0) return false;
|
|
|
+ return true;
|
|
|
+ } catch (err) {
|
|
|
+ console.warn(err);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...toRefs(data),
|
|
|
+ toggleDialog,
|
|
|
+ htmlRefHook,
|
|
|
+ jumpToAim,
|
|
|
+ dropdown,
|
|
|
+ dropdownRef,
|
|
|
+ resendMessage,
|
|
|
+ showReadReceiptTag,
|
|
|
+ readReceiptStyle,
|
|
|
+ readReceiptCont,
|
|
|
+ showReadReceiptDialog,
|
|
|
+ showRepliesDialog,
|
|
|
+ handleImageOrVideoBubbleStyle,
|
|
|
+ isEmojiReactionInMessage,
|
|
|
+ TIM,
|
|
|
+ };
|
|
|
+ },
|
|
|
+});
|
|
|
+export default messageBubble;
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import url('../../../styles/common.scss');
|
|
|
+@import url('../../../styles/icon.scss');
|
|
|
+.reverse {
|
|
|
+ flex-direction: row-reverse;
|
|
|
+ justify-content: flex-start;
|
|
|
+}
|
|
|
+.avatar {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 5px;
|
|
|
+}
|
|
|
+.message-bubble {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ padding-bottom: 5px;
|
|
|
+}
|
|
|
+.line-left {
|
|
|
+ border: 1px solid rgba(0, 110, 255, 0.5);
|
|
|
+}
|
|
|
+.message-reference-area {
|
|
|
+ display: flex;
|
|
|
+ background: #f2f2f2;
|
|
|
+ border-radius: 0.5rem;
|
|
|
+ border-radius: 0.63rem;
|
|
|
+ align-self: start;
|
|
|
+ margin-left: 44px;
|
|
|
+ margin-right: 8px;
|
|
|
+ &-show {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: inherit;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 6px;
|
|
|
+ p {
|
|
|
+ font-family: PingFangSC-Regular;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 0.88rem;
|
|
|
+ color: #999999;
|
|
|
+ letter-spacing: 0;
|
|
|
+ word-break: keep-all;
|
|
|
+ padding-right: 5px;
|
|
|
+ }
|
|
|
+ span {
|
|
|
+ height: 1.25rem;
|
|
|
+ font-family: PingFangSC-Regular;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 0.88rem;
|
|
|
+ color: #999999;
|
|
|
+ letter-spacing: 0;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.message-replies {
|
|
|
+ display: flex;
|
|
|
+ align-self: start;
|
|
|
+ margin-left: 44px;
|
|
|
+ margin-right: 8px;
|
|
|
+ padding: 2px;
|
|
|
+ color: #999999;
|
|
|
+ font-size: 10px;
|
|
|
+ i {
|
|
|
+ margin: 4px;
|
|
|
+ }
|
|
|
+ span {
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.message-reference-area-reverse,
|
|
|
+.message-replies-reverse {
|
|
|
+ align-self: end;
|
|
|
+ margin-right: 44px;
|
|
|
+ margin-left: 8px;
|
|
|
+}
|
|
|
+.message-img {
|
|
|
+ max-width: min(calc(100vw - 180px), 300px);
|
|
|
+ max-height: min(calc(100vw - 180px), 300px);
|
|
|
+}
|
|
|
+.message-video-cover {
|
|
|
+ display: inline-block;
|
|
|
+ position: relative;
|
|
|
+ &::before {
|
|
|
+ position: absolute;
|
|
|
+ z-index: 1;
|
|
|
+ content: '';
|
|
|
+ width: 0px;
|
|
|
+ height: 0px;
|
|
|
+ border: 15px solid transparent;
|
|
|
+ border-left: 20px solid #ffffff;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ bottom: 0;
|
|
|
+ right: 0;
|
|
|
+ margin: auto;
|
|
|
+ }
|
|
|
+}
|
|
|
+.message-videoimg {
|
|
|
+ max-width: min(calc(100vw - 160px), 300px);
|
|
|
+ max-height: min(calc(100vw - 160px), 300px);
|
|
|
+}
|
|
|
+.face-box {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.text-img {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+}
|
|
|
+.message-audio {
|
|
|
+ padding-left: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ position: relative;
|
|
|
+ .icon {
|
|
|
+ margin: 0 4px;
|
|
|
+ }
|
|
|
+ audio {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+.reserve {
|
|
|
+ flex-direction: row-reverse;
|
|
|
+}
|
|
|
+.message-area {
|
|
|
+ max-width: calc(100% - 54px);
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 0 8px;
|
|
|
+ .name {
|
|
|
+ padding-bottom: 4px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ color: #999999;
|
|
|
+ letter-spacing: 0;
|
|
|
+ }
|
|
|
+ .reference-content {
|
|
|
+ padding: 12px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: burlywood;
|
|
|
+ letter-spacing: 0;
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ animation: reference 800ms;
|
|
|
+ }
|
|
|
+ @-webkit-keyframes reference {
|
|
|
+ from {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ background-color: #ff9c19;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ @keyframes reference {
|
|
|
+ from {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ background-color: #ff9c19;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .content {
|
|
|
+ padding: 12px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #000000;
|
|
|
+ letter-spacing: 0;
|
|
|
+ word-wrap: break-word;
|
|
|
+ word-break: break-all;
|
|
|
+ width: fit-content;
|
|
|
+ &-in {
|
|
|
+ background: #fbfbfb;
|
|
|
+ border-radius: 0px 10px 10px 10px;
|
|
|
+ }
|
|
|
+ &-out {
|
|
|
+ background: #dceafd;
|
|
|
+ border-radius: 10px 0px 10px 10px;
|
|
|
+ }
|
|
|
+ &-image {
|
|
|
+ padding: 0px;
|
|
|
+ height: fit-content;
|
|
|
+ border-radius: 10px 0px 10px 10px;
|
|
|
+ }
|
|
|
+ &-video {
|
|
|
+ padding: 0px;
|
|
|
+ height: fit-content;
|
|
|
+ background: transparent;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.message-label {
|
|
|
+ align-self: flex-end;
|
|
|
+ font-family: PingFangSC-Regular;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #b6b8ba;
|
|
|
+ word-break: keep-all;
|
|
|
+}
|
|
|
+.fail {
|
|
|
+ width: 15px;
|
|
|
+ height: 15px;
|
|
|
+ border-radius: 15px;
|
|
|
+ background: red;
|
|
|
+ color: #ffffff;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.unRead {
|
|
|
+ color: #679ce1;
|
|
|
+}
|
|
|
+.dropdown-inner {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+}
|
|
|
+.dialog {
|
|
|
+ position: absolute;
|
|
|
+ z-index: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ width: fit-content;
|
|
|
+ &-right {
|
|
|
+ right: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|