Browse Source

修改样式

lex 1 year ago
parent
commit
787b25bdcd
100 changed files with 17845 additions and 0 deletions
  1. 2 0
      components.d.ts
  2. 37 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/index.ts
  3. 178 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/index.vue
  4. 91 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-audio.vue
  5. 747 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-bubble.vue
  6. 228 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-custom.vue
  7. 304 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-emoji-react.vue
  8. 81 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-face.vue
  9. 108 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-file.vue
  10. 258 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-image.vue
  11. 40 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-location.vue
  12. 53 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-merger.vue
  13. 248 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-reference.vue
  14. 64 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-revoked.vue
  15. 143 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-system.vue
  16. 46 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-text.vue
  17. 138 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-timestamp.vue
  18. 47 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-tip.vue
  19. 264 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-tool.vue
  20. 350 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-video.vue
  21. 1661 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/dist/server.js
  22. 105 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/index.ts
  23. 976 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/index.vue
  24. 37 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/interface.ts
  25. 5 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/index.ts
  26. 160 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage-member.vue
  27. 207 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage-name.vue
  28. 116 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage-notification.vue
  29. 880 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage.vue
  30. 160 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/member-profile.vue
  31. 122 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/color.scss
  32. 8 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/h5.scss
  33. 5 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/index.scss
  34. 329 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/web.scss
  35. 2 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/index.ts
  36. 247 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/index.vue
  37. 392 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-at.vue
  38. 96 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-button.vue
  39. 565 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-editor.vue
  40. 43 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-file.ts
  41. 221 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-reference-or-reply.vue
  42. 343 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/call.vue
  43. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/index.ts
  44. 45 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/style/h5.scss
  45. 4 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/style/index.scss
  46. 94 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/style/web.scss
  47. 165 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/custom/Custom.vue
  48. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/custom/index.ts
  49. 138 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/evaluate.vue
  50. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/index.ts
  51. 64 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/color.scss
  52. 81 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/h5.scss
  53. 5 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/index.scss
  54. 81 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/web.scss
  55. 151 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/Face.vue
  56. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/index.ts
  57. 6 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/color.scss
  58. 7 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/h5.scss
  59. 5 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/index.scss
  60. 48 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/web.scss
  61. 72 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/file/file.vue
  62. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/file/index.ts
  63. 169 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/forward/forward.vue
  64. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/forward/index.ts
  65. 71 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/image/image.vue
  66. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/image/index.ts
  67. 416 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/imagePreviewer/imagePreviewer.vue
  68. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/imagePreviewer/index.ts
  69. 162 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/location/Location.vue
  70. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/location/index.ts
  71. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/index.ts
  72. 339 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/readReceiptDialog.vue
  73. 141 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/style/h5.scss
  74. 4 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/style/index.scss
  75. 164 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/style/web.scss
  76. 2 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/index.ts
  77. 312 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/replies-item.vue
  78. 156 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/replies.vue
  79. 143 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/style/h5.scss
  80. 4 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/style/index.scss
  81. 122 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/style/web.scss
  82. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/typingHeader/index.ts
  83. 210 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/typingHeader/typingHeader.vue
  84. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/video/index.ts
  85. 72 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/video/video.vue
  86. 3 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/index.ts
  87. 59 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/color.scss
  88. 52 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/h5.scss
  89. 5 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/index.scss
  90. 49 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/web.scss
  91. 122 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/words.vue
  92. 1354 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/server.ts
  93. 151 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/style/color.scss
  94. 219 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/style/dist/h5.css
  95. 261 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/style/h5.scss
  96. 5 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/style/index.scss
  97. 526 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/style/web.scss
  98. 61 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/utils/decodeText.ts
  99. 1078 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/utils/dist/utils.js
  100. 304 0
      src/TUIKit/TUIComponents/container/TUIChat-temp/utils/emojiMap.ts

+ 2 - 0
components.d.ts

@@ -11,6 +11,8 @@ declare module '@vue/runtime-core' {
   export interface GlobalComponents {
     Application: typeof import('./src/components/Application/Application.vue')['default']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
+    NTabPane: typeof import('naive-ui')['NTabPane']
+    NTabs: typeof import('naive-ui')['NTabs']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     VanButton: typeof import('vant/es')['Button']

+ 37 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/index.ts

@@ -0,0 +1,37 @@
+import MessageText from './message-text.vue';
+import MessageImage from './message-image.vue';
+import MessageVideo from './message-video.vue';
+import MessageAudio from './message-audio.vue';
+import MessageFile from './message-file.vue';
+import MessageFace from './message-face.vue';
+import MessageLocation from './message-location.vue';
+import MessageMerger from './message-merger.vue';
+import MessageCustom from './message-custom.vue';
+import MessageTip from './message-tip.vue';
+import MessageBubble from './message-bubble.vue';
+import MessageRevoked from './message-revoked.vue';
+import MessageSystem from './message-system.vue';
+import MessageTool from './message-tool.vue';
+import MessageEmojiReact from './message-emoji-react.vue';
+import MessageItem from './index.vue';
+import MessageTimestamp from './message-timestamp.vue'
+
+export {
+  MessageText,
+  MessageImage,
+  MessageVideo,
+  MessageAudio,
+  MessageFile,
+  MessageFace,
+  MessageLocation,
+  MessageMerger,
+  MessageCustom,
+  MessageTip,
+  MessageTimestamp,
+  MessageBubble,
+  MessageRevoked,
+  MessageSystem,
+  MessageTool,
+  MessageEmojiReact,
+  MessageItem
+};

+ 178 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/index.vue

@@ -0,0 +1,178 @@
+<template>
+  <div class="message-item">
+    <MessageTip v-if="isMessageTip(message)" :data="handleTipMessageShowContext(message)" />
+    <MessageBubble
+      v-else-if="!message.isRevoked"
+      :data="message"
+      :isH5="env.isH5"
+      :messagesList="messageList"
+      :needGroupReceipt="displayGroupMessageReadReceipt"
+      :needReplies="true"
+      :needEmojiReact="displayEmojiReactions"
+      @jumpID="jumpID"
+      @resendMessage="resendMessage"
+      @showReadReceiptDialog="showReadReceiptDialog"
+      @showRepliesDialog="showRepliesDialog"
+    >
+      <MessageText v-if="message.type === types.MSG_TEXT" :data="handleTextMessageShowContext(message)" />
+      <MessageImage
+        v-if="message.type === types.MSG_IMAGE"
+        :isH5="env.isH5"
+        :data="handleImageMessageShowContext(message)"
+        @uploading="uploading"
+        @previewImage="previewImage"
+      />
+      <MessageVideo
+        v-if="message.type === types.MSG_VIDEO"
+        :isH5="env.isH5"
+        :data="handleVideoMessageShowContext(message)"
+        @uploading="uploading"
+      />
+      <MessageAudio v-if="message.type === types.MSG_AUDIO" :data="handleAudioMessageShowContext(message)" />
+      <MessageFile v-if="message.type === types.MSG_FILE" :data="handleFileMessageShowContext(message)" />
+      <MessageFace v-if="message.type === types.MSG_FACE" :data="handleFaceMessageShowContext(message)" :isH5="env.isH5"/>
+      <MessageLocation v-if="message.type === types.MSG_LOCATION" :data="handleLocationMessageShowContext(message)" />
+      <MessageCustom v-if="message.type === types.MSG_CUSTOM" :data="handleCustomMessageShowContext(message)" />
+      <MessageMerger v-if="message.type === types.MSG_MERGER" :data="handleMergerMessageShowContext(message)" />
+      <template #dialog>
+        <MessageTool :message="message" :needEmojiReact="displayEmojiReactions" @handleMessage="handleMessage" />
+        <MessageEmojiReact v-if="!env?.isH5 && displayEmojiReactions" :message="message" type="dropdown" />
+      </template>
+    </MessageBubble>
+    <MessageRevoked v-else :isEdit="message.type === types.MSG_TEXT" :data="message" @edit="handleEdit(message)" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { toRefs, defineProps, defineEmits } from 'vue';
+import MessageBubble from './message-bubble.vue';
+import MessageText from './message-text.vue';
+import MessageImage from './message-image.vue';
+import MessageVideo from './message-video.vue';
+import MessageAudio from './message-audio.vue';
+import MessageFile from './message-file.vue';
+import MessageFace from './message-face.vue';
+import MessageLocation from './message-location.vue';
+import MessageMerger from './message-merger.vue';
+import MessageCustom from './message-custom.vue';
+import MessageTip from './message-tip.vue';
+import MessageTool from './message-tool.vue';
+import MessageEmojiReact from './message-emoji-react.vue';
+import MessageRevoked from './message-revoked.vue';
+
+import {
+  handleTextMessageShowContext,
+  handleImageMessageShowContext,
+  handleVideoMessageShowContext,
+  handleAudioMessageShowContext,
+  handleFileMessageShowContext,
+  handleFaceMessageShowContext,
+  handleLocationMessageShowContext,
+  handleMergerMessageShowContext,
+  handleTipMessageShowContext,
+  handleCustomMessageShowContext,
+  isMessageTip,
+} from '../utils/utils';
+import { Message } from '@tencentcloud/chat';
+import constant from '../../constant';
+
+const props = defineProps({
+  message: {
+    type: Object,
+    default: () => ({}),
+  },
+  beforeMessage: {
+    type: Object,
+    default: () => ({}),
+  },
+  types: {
+    type: Object,
+    default: () => ({}),
+  },
+  env: {
+    type: Object,
+    default: () => ({}),
+  },
+  messageList: {
+    type: Array,
+    default: () => [],
+  },
+  // 开关控制位
+  displayGroupMessageReadReceipt: {
+    type: Boolean,
+    default: true,
+  },
+  displayEmojiReactions: {
+    type: Boolean,
+    default: true,
+  },
+});
+
+const emits = defineEmits(['handleEditor', 'showDialog', 'uploading', 'jumpID','resendMessage']);
+const { message, types, env, messageList, displayGroupMessageReadReceipt, displayEmojiReactions } = toRefs(props);
+
+const handleEdit = (message: any) => {
+  emits('handleEditor', message, 'reedit');
+};
+
+const handleMessage = (message: Message, type: string) => {
+  if (!message || !type) {
+    return;
+  }
+  switch (type) {
+    case constant.handleMessage.forward:
+      emits('showDialog', message, constant.handleMessage.forward);
+      break;
+    case constant.handleMessage.reference:
+      emits('handleEditor', message, constant.handleMessage.reference);
+      break;
+    case constant.handleMessage.reply:
+      emits('handleEditor', message, constant.handleMessage.reply);
+      break;
+    default:
+      break;
+  }
+};
+
+const previewImage = (message: Message) => {
+  if (message) {
+    emits('showDialog', message, 'previewImage');
+  }
+};
+
+const showReadReceiptDialog = (message: Message) => {
+  if (message) {
+    emits('showDialog', message, 'receipt');
+  }
+};
+
+const showRepliesDialog = (message: Message) => {
+  if (message) {
+    emits('showDialog', message, 'replies');
+  }
+};
+
+const jumpID = (messageID: string) => {
+  if (messageID) {
+    emits('jumpID', messageID);
+  }
+};
+
+const uploading = () => {
+  emits('uploading');
+};
+
+const resendMessage = (message: Message) => {
+  if (message) {
+    emits('resendMessage', message);
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-item {
+  display: flex;
+  flex-direction: column;
+}
+</style>

+ 91 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-audio.vue

@@ -0,0 +1,91 @@
+<template>
+  <div
+    class="message-audio"
+    :class="[data.message.flow === 'out' && 'reserve']"
+    @click.stop="play"
+    :style="`width: ${data?.second * 10 + 40}Px`"
+  >
+    <i
+      class="icon icon-voice"
+      :class="[data.message.flow === 'out' && 'icon-reserve']"
+    ></i>
+    <label>{{ data.second }}s</label>
+    <audio ref="audio" :src="data.url"></audio>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watch, reactive, toRefs, ref } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {},
+      show: false
+    });
+
+    const audio = ref(null);
+
+    watch(
+      () => props.data,
+      () => {
+        data.data = props.data;
+      },
+      { deep: true, immediate: true }
+    );
+
+    const play = () => {
+      const audios = document.getElementsByTagName('audio');
+      for (const audio of audios) {
+        if (!audio.paused) {
+          audio.pause();
+          audio.load();
+        }
+      }
+      const audioPlayer: any = audio.value;
+      if (audioPlayer.paused) {
+        audioPlayer.play();
+        data.show = true;
+      } else {
+        audioPlayer.pause();
+        audioPlayer.load();
+        data.show = false;
+      }
+    };
+
+    return {
+      ...toRefs(data),
+      audio,
+      play
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-audio {
+  display: flex;
+  align-items: center;
+  position: relative;
+  cursor: pointer;
+  max-width: 100%;
+  overflow: hidden;
+  .icon {
+    margin: 0 4Px;
+  }
+  audio {
+    width: 0;
+    height: 0;
+  }
+}
+.reserve {
+  flex-direction: row-reverse;
+}
+</style>

+ 747 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-bubble.vue

@@ -0,0 +1,747 @@
+<template>
+  <div
+    class="message-bubble"
+    :class="[message.flow === 'in' ? '' : 'reverse']"
+    ref="htmlRefHook"
+  >
+    <img
+      class="avatar"
+      :src="
+        message?.avatar ||
+        'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+      "
+      onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.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>

+ 228 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-custom.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="custom">
+    <template v-if="isCustom === constant.typeService">
+      <div>
+        <h1>
+          <label>{{ extension.title }}</label>
+          <a
+            v-if="extension.hyperlinks_text"
+            :href="extension.hyperlinks_text.value"
+            target="view_window"
+          >
+            {{ extension.hyperlinks_text.key }}
+          </a>
+        </h1>
+        <ul v-if="extension.item && extension.item.length > 0">
+          <li v-for="(item, index) in extension.item" :key="index">
+            <a v-if="isUrl(item.value)" :href="item.value" target="view_window">
+              {{ item.key }}
+            </a>
+            <p v-else>{{ item.key }}</p>
+          </li>
+        </ul>
+        <article>{{ extension.description }}</article>
+      </div>
+    </template>
+    <template v-else-if="isCustom.businessID === constant.typeEvaluate">
+      <div class="evaluate">
+        <h1>{{ $t('message.custom.对本次服务评价') }}</h1>
+        <ul>
+          <li
+            class="evaluate-list-item"
+            v-for="(item, index) in ~~isCustom.score"
+            :key="index"
+          >
+            <i class="icon icon-star-light"></i>
+          </li>
+        </ul>
+        <article>{{ isCustom.comment }}</article>
+      </div>
+    </template>
+    <template v-else-if="isCustom.businessID === constant.typeOrder">
+      <div class="order" @click="openLink(isCustom.link)">
+        <img :src="isCustom.imageUrl" alt="" />
+        <main>
+          <h1>{{ isCustom.title }}</h1>
+          <p>{{ isCustom.description }}</p>
+          <span>{{ isCustom.price }}</span>
+        </main>
+      </div>
+    </template>
+    <template v-else-if="isCustom.businessID === constant.typeTextLink">
+      <div class="textLink">
+        <p>{{ isCustom.text }}</p>
+        <a :href="isCustom.link" target="view_window">
+          {{ $t('message.custom.查看详情>>') }}
+        </a>
+      </div>
+    </template>
+    <template v-else-if="isCustom.businessID === constant.TYPE_CALL_MESSAGE">
+      <div
+        class="call"
+        @click="handleCallAgain"
+        :class="`call-${data?.message?.conversationType}`"
+      >
+        <i class="icon" :class="handleCallMessageIcon()"></i>
+        <span>{{ data.custom }}</span>
+      </div>
+    </template>
+    <template v-else>
+      <span v-html="data.custom"></span>
+    </template>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+import { isUrl, JSONToObject } from '../utils/utils';
+import constant from '../../constant';
+import { useStore } from 'vuex';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const VuexStore =
+      ((window as any)?.TUIKitTUICore?.isOfficial && useStore && useStore()) ||
+      {};
+    const data = reactive({
+      data: {} as any,
+      extension: {},
+      isCustom: '',
+      constant: constant
+    });
+    watchEffect(() => {
+      data.data = props.data;
+      const {
+        message: { payload }
+      } = props.data;
+      data.isCustom = payload.data || ' ';
+      data.isCustom = JSONToObject(payload.data);
+      if (payload.data === constant.typeService) {
+        data.extension = JSONToObject(payload.extension);
+      }
+    });
+    const openLink = (url: any) => {
+      window.open(url);
+    };
+    const handleCallMessageIcon = () => {
+      const callType = JSON.parse(
+        JSON.parse(data?.data?.message?.payload?.data)?.data
+      )?.call_type;
+      let className = '';
+      switch (callType) {
+        case 1:
+          className = 'icon-call-voice';
+          break;
+        case 2:
+          className = 'icon-call-video';
+          break;
+        default:
+          break;
+      }
+      return className;
+    };
+
+    const handleCallAgain = async () => {
+      const callType = JSON.parse(
+        JSON.parse(props?.data?.message?.payload?.data)?.data
+      )?.call_type;
+      switch (data?.data?.message?.conversationType) {
+        case (window as any).TUIKitTUICore.TIM.TYPES.CONV_C2C:
+          // eslint-disable-next-line no-case-declarations, no-unsafe-optional-chaining
+          const { flow, to, from } = data?.data?.message;
+          if (to === from) break;
+          try {
+            await (window as any)?.TUIKitTUICore?.TUIServer?.TUICallKit?.call({
+              userID: flow === 'out' ? to : from,
+              type: callType
+            });
+            (window as any)?.TUIKitTUICore?.isOfficial &&
+              VuexStore?.commit &&
+              VuexStore?.commit('handleTask', 6);
+          } catch (error) {
+            console.warn(error);
+          }
+          break;
+        case (window as any).TUIKitTUICore.TIM.TYPES.CONV_GROUP:
+          break;
+        default:
+          break;
+      }
+    };
+
+    return {
+      ...toRefs(data),
+      isUrl,
+      openLink,
+      handleCallMessageIcon,
+      handleCallAgain
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+a {
+  color: #679ce1;
+}
+.custom {
+  font-size: 14Px;
+  h1 {
+    font-size: 14Px;
+    color: #000000;
+  }
+  h1,
+  a,
+  p {
+    font-size: 14Px;
+  }
+  .evaluate {
+    ul {
+      display: flex;
+      padding-top: 10Px;
+    }
+  }
+  .order {
+    display: flex;
+    main {
+      padding-left: 5Px;
+      p {
+        font-family: PingFangSC-Regular;
+        width: 145Px;
+        line-height: 17Px;
+        font-size: 14Px;
+        color: #999999;
+        letter-spacing: 0;
+        margin-bottom: 6Px;
+        word-break: break-word;
+      }
+      span {
+        font-family: PingFangSC-Regular;
+        line-height: 25Px;
+        color: #ff7201;
+      }
+    }
+    img {
+      width: 67Px;
+      height: 67Px;
+    }
+  }
+  .call {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    &-C2C {
+      cursor: pointer;
+    }
+    &-GROUP {
+      cursor: default;
+    }
+  }
+}
+</style>

+ 304 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-emoji-react.vue

@@ -0,0 +1,304 @@
+<template>
+  <div
+    class="dialog-emoji"
+    :class="env?.isH5 ? 'dialog-emoji-h5' : ''"
+    v-if="type === 'dropdown'"
+  >
+    <div class="face-list collapse">
+      <ul class="face-list-collapse">
+        <li
+          class="face-list-item"
+          v-for="(childrenItem, childrenIndex) in emojiCollapseList"
+          :key="childrenIndex"
+          @click.stop="select(childrenItem)"
+        >
+          <img :src="emojiUrl + emojiMap[childrenItem]" />
+        </li>
+      </ul>
+      <div class="face-list-button" @click.stop="isCollapse = !isCollapse">
+        <i
+          class="icon"
+          :class="[isCollapse ? 'icon-expand' : 'icon-collapse']"
+        ></i>
+      </div>
+    </div>
+    <ul class="face-list face-list-expand" v-show="!isCollapse">
+      <li
+        class="face-list-item"
+        v-for="(childrenItem, childrenIndex) in emojiExpandList"
+        :key="childrenIndex"
+        @click.stop="select(childrenItem)"
+      >
+        <img :src="emojiUrl + emojiMap[childrenItem]" />
+      </li>
+    </ul>
+  </div>
+  <div v-if="type === 'content'" class="emoji-content">
+    <ul class="emoji-react" ref="container">
+      <li
+        class="emoji-react-item"
+        v-for="(val, key) in handleEmojiReact(message)"
+        :key="key"
+        v-show="val && val?.length"
+        @click.stop="select(key)"
+      >
+        <img :src="emojiUrl + emojiMap[key]" />
+        <div class="emoji-react-item-content">
+          <span>{{ handleEmojiReactItem(val) }}</span>
+        </div>
+      </li>
+    </ul>
+  </div>
+</template>
+<script lang="ts">
+import {
+  defineComponent,
+  watch,
+  reactive,
+  toRefs,
+  computed,
+  ref,
+  onMounted,
+  nextTick
+} from 'vue';
+import TIM from '../../../../TUICore/tim';
+import { emojiUrl, emojiMap, emojiName } from '../utils/emojiMap';
+import { JSONToObject } from '../utils/utils';
+import TUIChat from '../index.vue';
+import { Message } from '@tencentcloud/chat';
+export default defineComponent({
+  props: {
+    message: {
+      type: Object,
+      default: () => ({})
+    },
+    emojiList: {
+      type: Array,
+      default: () => []
+    },
+    type: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleCollapse'],
+  setup(props: any, ctx: any) {
+    const { TUIServer } = TUIChat;
+    const data = reactive({
+      message: {} as Message,
+      emojiUrl,
+      emojiMap,
+      emojiName,
+      isCollapse: true,
+      types: TIM.TYPES,
+      env: TUIServer.TUICore.TUIEnv,
+      type: props.type,
+      emojiReacts: {},
+      allMemberList: []
+    });
+
+    const container = ref({} as HTMLElement);
+
+    const emojiCollapseList = computed(() => {
+      return data.emojiName.slice(0, 6);
+    });
+
+    const emojiExpandList = computed(() => {
+      return data.emojiName.slice(6);
+    });
+
+    const select = (item: any) => {
+      TUIServer.emojiReact(data.message, item);
+    };
+
+    const handleEmojiReact = (message: any) => {
+      try {
+        if (!message?.cloudCustomData) return;
+        const reactList = JSONToObject(message?.cloudCustomData)?.messageReact
+          ?.reacts;
+        if (!reactList) return;
+        data.emojiReacts = reactList;
+        return reactList;
+      } catch (err) {
+        console.warn(err);
+      }
+    };
+
+    const handleEmojiReactItem = (userIDList: any) => {
+      let res = '';
+      userIDList?.forEach((item: any, index: any) => {
+        const memberInfo = data.allMemberList?.find(
+          (member: any) => member.userID === item
+        );
+        res = res + (memberInfo ? (memberInfo as any)?.nick : item) + ', ';
+      });
+      if (res.length) res = res.substring(0, res.lastIndexOf(','));
+      return res;
+    };
+
+    const handleAllMemberList = (message: any) => {
+      const TUIChatStore = TUIServer?.currentStore;
+      switch (message?.conversationType) {
+        case TIM.TYPES.CONV_C2C:
+          data.allMemberList = [
+            {
+              userID: TUIChatStore?.conversation?.userProfile?.userID,
+              nick: TUIChatStore?.conversation?.userProfile?.nick
+            },
+            {
+              userID: TUIChatStore?.selfInfo?.userID,
+              nick: TUIChatStore?.selfInfo?.nick
+            }
+          ] as any;
+          break;
+        case TIM.TYPES.CONV_GROUP:
+          data.allMemberList = TUIChatStore?.allMemberList?.memberList;
+          break;
+        default:
+          break;
+      }
+    };
+
+    watch(
+      () => props.message,
+      (newVal: any, oldVal: any) => {
+        data.message = props.message;
+        if (newVal?.conversationID !== oldVal?.conversationID) {
+          handleAllMemberList(newVal);
+        }
+      },
+      { deep: true, immediate: true }
+    );
+
+    watch(
+      () => data.isCollapse,
+      (newVal, oldVal) => {
+        if (newVal === oldVal) return;
+        if (!data?.env?.isH5) return;
+        ctx.emit('handleCollapse', newVal);
+      }
+    );
+
+    return {
+      ...toRefs(data),
+      emojiCollapseList,
+      emojiExpandList,
+      select,
+      handleEmojiReact,
+      handleEmojiReactItem,
+      container
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.dialog-emoji {
+  margin-left: 2Px;
+  background: #ffffff;
+  border: 1Px solid #e0e0e0;
+  box-shadow: 0 4Px 12Px 0 rgba(0, 0, 0, 0.06);
+  width: 242Px;
+  min-height: 28Px;
+  height: fit-content;
+  padding: 2Px 6Px;
+  word-break: keep-all;
+  top: 30Px;
+  border-radius: 8Px;
+  &-h5 {
+    margin: 0 5Px;
+    border: none;
+    border-bottom: 1Px solid #e0e0e0;
+    box-shadow: none;
+    border-radius: 10Px 10Px 0 0;
+  }
+  .collapse {
+    padding: 2Px 0Px;
+  }
+  .face-list {
+    display: flex;
+    overflow: hidden;
+    flex-wrap: nowrap;
+    flex-direction: row;
+    &-collapse {
+      display: flex;
+      overflow: hidden;
+      flex-wrap: nowrap;
+      justify-content: space-between;
+      align-items: center;
+      flex: 1;
+    }
+    &-expand {
+      border-top: 1Px solid #e0e0e0;
+      display: flex;
+      overflow-x: hidden;
+      flex-wrap: wrap;
+      max-height: 90Px;
+      overflow-y: auto;
+    }
+    &-button {
+      padding: 5Px 7Px;
+      height: 20Px;
+      line-height: 20Px;
+      text-align: center;
+      align-items: center;
+      i {
+        text-align: center;
+      }
+    }
+    li {
+      margin: 4Px;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      flex-direction: row;
+      img {
+        width: 22Px;
+        height: 22Px;
+      }
+    }
+  }
+}
+.emoji-content {
+  .emoji-react {
+    margin-top: 3Px;
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    align-items: center;
+    &-item {
+      width: fit-content;
+      height: 20Px;
+      background: rgba(0, 0, 0, 0.05);
+      border-radius: 12Px;
+      padding: 2Px 6Px;
+      margin: 2Px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      max-width: 200Px;
+      display: flex;
+      img {
+        width: 16Px;
+        height: 16Px;
+        padding: 2Px;
+      }
+      &-content {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        font-size: 10Px;
+        color: #999999;
+        align-self: center;
+        span {
+          margin: 2Px;
+          font-size: 10Px;
+          color: #999999;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+}
+</style>

+ 81 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-face.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="message-image" ref="skeleton">
+    <img
+      class="message-img"
+      :src="data.url"
+      :style="
+        isH5
+          ? {
+              maxWidth: data.width ? data.width + 'Px' : 'calc(100vw - 180Px)',
+              maxHeight: data.height
+                ? data.height + 'Px'
+                : 'calc(100vw - 180Px)'
+            }
+          : {}
+      "
+    />
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  watchEffect,
+  reactive,
+  toRefs,
+  nextTick,
+  ref
+} from 'vue';
+import { handleSkeletonSize } from '../utils/utils';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    isH5: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {}
+    });
+
+    const skeleton: any = ref();
+
+    watchEffect(() => {
+      data.data = props.data;
+      if (!data.data) return;
+      nextTick(() => {
+        const containerWidth =
+          document.getElementById('messageEle')?.clientWidth || 0;
+        const max = props.isH5 ? Math.min(containerWidth - 172, 300) : 300;
+        const size = handleSkeletonSize(240, 240, max, max);
+        skeleton?.value?.style &&
+          (skeleton.value.style.width = `${size.width}Px`);
+        skeleton?.value?.style &&
+          (skeleton.value.style.height = `${size.height}Px`);
+      });
+    });
+    return {
+      ...toRefs(data),
+      skeleton
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.text-img {
+  width: 20Px;
+  height: 20Px;
+}
+.message-img {
+  max-width: min(calc(100vw - 180Px), 300Px);
+  max-height: min(calc(100vw - 180Px), 300Px);
+}
+</style>

+ 108 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-file.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="message-file">
+    <div class="box" @click="download" :title="$t('TUIChat.单击下载')">
+      <i class="icon icon-files"></i>
+      <div class="message-file-content">
+        <label>{{ data.name }}</label>
+        <span>{{ data.size }}</span>
+      </div>
+    </div>
+    <progress v-if="data.progress" :value="data.progress" max="1"></progress>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {},
+    });
+
+    watchEffect(() => {
+      data.data = props.data;
+    });
+
+    const download = () => {
+      const file: any = data.data;
+      const option: any = {
+        mode: 'cors',
+        headers: new Headers({
+          'Content-Type': 'application/x-www-form-urlencoded',
+        }),
+      };
+      // 浏览器支持fetch则用blob下载,避免浏览器点击a标签,跳转到新页面预览的行为
+      // If the browser supports fetch, use blob to download, so as to avoid the browser clicking the a tag and jumping to the preview of the new page
+      if ((window as any).fetch) {
+        fetch(file.url, option)
+          .then((res) => res.blob())
+          .then((blob) => {
+            const a = document.createElement('a');
+            const url = window.URL.createObjectURL(blob);
+            a.href = url;
+            a.download = file.name;
+            a.click();
+          });
+      } else {
+        const a = document.createElement('a');
+        a.href = file.url;
+        a.target = '_blank';
+        a.download = file.name;
+        a.click();
+      }
+    };
+
+    return {
+      ...toRefs(data),
+      download,
+    };
+  },
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-file {
+  display: flex;
+  flex-direction: column;
+  .box {
+    flex: 1;
+    display: flex;
+    cursor: pointer;
+    .message-file-content {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+    }
+  }
+  progress {
+    width: 100%;
+    color: #006eff;
+    appearance: none;
+    border-radius: 0.25rem;
+    background: rgba(#ffffff, 1);
+    width: 100%;
+    height: 0.5rem;
+    &::-webkit-progress-value {
+      background-color: #006eff;
+      border-radius: 0.25rem;
+    }
+    &::-webkit-progress-bar {
+      border-radius: 0.25rem;
+      background: rgba(#ffffff, 1);
+    }
+    &::-moz-progress-bar {
+      color: #006eff;
+      background: #006eff;
+      border-radius: 0.25rem;
+    }
+  }
+}
+</style>

+ 258 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-image.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="message-image" @click.self="toggleShow" ref="skeleton">
+    <img
+      class="message-img"
+      :src="data.url"
+      :width="data.width"
+      :height="data.height"
+      :style="
+        isH5
+          ? {
+              maxWidth: data.width ? data.width + 'Px' : 'calc(100vw - 180Px)',
+              maxHeight: data.height
+                ? data.height + 'Px'
+                : 'calc(100vw - 180Px)'
+            }
+          : {}
+      "
+    />
+    <div class="progress" v-if="data.progress">
+      <progress :value="data.progress" max="1"></progress>
+    </div>
+    <div class="dialog" v-if="show" @click.self="toggleShow">
+      <header v-if="!isH5">
+        <i class="icon icon-close" @click.stop="toggleShow"></i>
+      </header>
+      <div
+        class="dialog-box"
+        :class="[isH5 ? 'dialog-box-h5' : '']"
+        @click.self="toggleShow"
+      >
+        <img
+          :class="[isWidth ? 'isWidth' : 'isHeight']"
+          :src="data.message.payload.imageInfoArray[0].url"
+          @click.self="toggleShow"
+        />
+        <div v-if="isH5" class="dialog-box-h5-footer">
+          <p @click="toggleShow">
+            <img src="../../../assets/icon/close-image.png" />
+          </p>
+          <p @click.stop="downloadImage(data.message)">
+            <img src="../../../assets/icon/downaload-image.png" />
+          </p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  watchEffect,
+  reactive,
+  toRefs,
+  computed,
+  ref,
+  nextTick
+} from 'vue';
+import { handleSkeletonSize } from '../utils/utils';
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    isH5: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['uploading', 'previewImage'],
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {
+        progress: 0
+      },
+      show: false
+    });
+
+    const skeleton: any = ref();
+
+    const isWidth = computed(() => {
+      // eslint-disable-next-line no-unsafe-optional-chaining
+      const { width = 0, height = 0 } = (data.data as any)?.message?.payload
+        ?.imageInfoArray[0];
+      return width >= height;
+    });
+
+    watchEffect(() => {
+      data.data = props.data;
+      if (!data.data) return;
+      nextTick(() => {
+        if (!data.data.progress) {
+          const { width = 0, height = 0 } = data.data as any;
+          if (width === 0 || height === 0) return;
+          const containerWidth =
+            document.getElementById('app')?.clientWidth || 0;
+          const max = props.isH5 ? Math.min(containerWidth - 180, 300) : 300;
+          const size = handleSkeletonSize(width, height, max, max);
+          skeleton?.value?.style &&
+            (skeleton.value.style.width = `${size.width}Px`);
+          skeleton?.value?.style &&
+            (skeleton.value.style.height = `${size.height}Px`);
+        } else {
+          ctx.emit('uploading');
+        }
+      });
+    });
+
+    const toggleShow = () => {
+      if (!data.data.progress) {
+        ctx.emit('previewImage', (data.data as any).message);
+      }
+    };
+    const downloadImage = (message: any) => {
+      const targetImage = document.createElement('a');
+      const downloadImageName = message.payload.imageInfoArray[0].instanceID;
+      targetImage.setAttribute('download', downloadImageName);
+      const image = new Image();
+      image.src = message.payload.imageInfoArray[0].url;
+      image.setAttribute('crossOrigin', 'Anonymous');
+      image.onload = () => {
+        targetImage.href = getImageDataURL(image);
+        targetImage.click();
+      };
+    };
+
+    const getImageDataURL = (image: any) => {
+      const canvas = document.createElement('canvas');
+      canvas.width = image.width;
+      canvas.height = image.height;
+      const ctx = canvas.getContext('2d');
+      ctx?.drawImage(image, 0, 0, image.width, image.height);
+      const extension = image.src
+        .substring(image.src.lastIndexOf('.') + 1)
+        .toLowerCase();
+      return canvas.toDataURL(`image/${extension}`, 1);
+    };
+
+    return {
+      ...toRefs(data),
+      toggleShow,
+      skeleton,
+      isWidth,
+      downloadImage
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-image {
+  position: relative;
+  overflow: hidden;
+  opacity: 1;
+  .message-img {
+    max-width: min(calc(100vw - 180Px), 300Px);
+    max-height: min(calc(100vw - 180Px), 300Px);
+    width: inherit;
+    height: inherit;
+  }
+  .progress {
+    position: absolute;
+    box-sizing: border-box;
+    width: 100%;
+    height: 100%;
+    padding: 0 20Px;
+    left: 0;
+    top: 0;
+    background: rgba(#000000, 0.5);
+    display: flex;
+    align-items: center;
+    progress {
+      color: #006eff;
+      appearance: none;
+      border-radius: 0.25rem;
+      background: rgba(#ffffff, 1);
+      width: 100%;
+      height: 0.5rem;
+      &::-webkit-progress-value {
+        background-color: #006eff;
+        border-radius: 0.25rem;
+      }
+      &::-webkit-progress-bar {
+        border-radius: 0.25rem;
+        background: rgba(#ffffff, 1);
+      }
+      &::-moz-progress-bar {
+        color: #006eff;
+        background: #006eff;
+        border-radius: 0.25rem;
+      }
+    }
+  }
+}
+.dialog {
+  position: fixed;
+  z-index: 12;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(#000000, 0.3);
+  top: 0;
+  left: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  header {
+    display: flex;
+    justify-content: flex-end;
+    background: rgba(0, 0, 0, 0.49);
+    width: 100%;
+    box-sizing: border-box;
+    padding: 10Px 10Px;
+  }
+  &-box {
+    display: flex;
+    flex: 1;
+    max-height: 100%;
+    padding: 6rem;
+    box-sizing: border-box;
+    justify-content: center;
+    align-items: center;
+  }
+}
+.dialog-box-h5 {
+  width: 100%;
+  height: 100%;
+  background: #000000;
+  padding: 30Px 0;
+  display: flex;
+  flex-direction: column;
+  &-footer {
+    position: fixed;
+    bottom: 10Px;
+    display: flex;
+    width: 90vw;
+    justify-content: space-between;
+    p {
+      width: 3.88rem;
+      height: 3.88rem;
+    }
+    img {
+      width: 100%;
+    }
+    i {
+      padding: 20Px;
+    }
+  }
+}
+
+.isWidth {
+  width: 100%;
+}
+.isHeight {
+  height: 100%;
+}
+</style>

+ 40 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-location.vue

@@ -0,0 +1,40 @@
+<template>
+  <a class="message-location" :href="data.href" target="_blank" title="点击查看详情">
+    <span class="el-icon-location-outline">{{data.description}}</span>
+    <img :src="data.url" />
+  </a>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  setup(props:any, ctx:any) {
+    const data = reactive({
+      data: {},
+    });
+
+    watchEffect(() => {
+      data.data = props.data;
+    });
+
+    return {
+      ...toRefs(data),
+    };
+  },
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-location {
+  display: flex;
+  flex-direction: column;
+}
+</style>

+ 53 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-merger.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="message-merger">
+    <div class="merger-box" :data-value="data.message">
+      <p class="merger-title">{{ data.title }}</p>
+      <p
+        class="merger-text"
+        v-for="(item, index) in data.abstractList"
+        :key="index"
+      >
+        {{ item }}
+      </p>
+    </div>
+    <span class="merger-label">聊天记录</span>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {}
+    });
+
+    watchEffect(() => {
+      data.data = props.data;
+    });
+    return {
+      ...toRefs(data)
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-merger {
+  display: flex;
+  flex-direction: column;
+  .merger-label {
+    border-top: 1Px solid #dddddd;
+    margin-top: 10Px;
+    padding-top: 5Px;
+  }
+}
+</style>

+ 248 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-reference.vue

@@ -0,0 +1,248 @@
+<template>
+  <div
+    v-if="
+      referenceMessage?.messageID &&
+      allMessageID.indexOf(referenceMessage.messageID) !== -1
+    "
+    :class="`${type} ${message.flow === 'in' && type + '-reverse'}`"
+  >
+    <p class="message-reference-user">
+      <span class="nick">
+        {{ referenceForShow.nick || referenceForShow.from }}
+      </span>
+      <span class="colon">{{ type === 'reference' ? ':' : '' }}</span>
+    </p>
+    <div
+      class="face-box"
+      v-if="referenceMessage.messageType === constant.typeText"
+    >
+      <div v-for="(item, index) in face" :key="index">
+        <span class="text-box" v-if="item.name === 'text'">
+          {{ item.text }}
+        </span>
+        <img class="text-img" v-else-if="item.name === 'img'" :src="item.src" />
+      </div>
+    </div>
+    <span v-if="referenceMessage.messageType === constant.typeCustom">
+      {{ referenceMessage.messageAbstract }}
+    </span>
+    <img
+      v-if="referenceMessage.messageType === constant.typeImage"
+      class="message-img"
+      :src="referenceForShow.payload.imageInfoArray[1].url"
+    />
+    <div
+      v-if="referenceMessage.messageType === constant.typeVideo"
+      class="message-video-cover"
+    >
+      <img
+        class="message-videoimg"
+        :src="
+          referenceForShow?.payload?.snapshotUrl ||
+          referenceForShow?.payload?.thumbUrl
+        "
+      />
+    </div>
+    <img
+      v-if="referenceMessage.messageType === constant.typeFace"
+      class="message-img"
+      :src="url"
+    />
+    <span
+      v-if="
+        referenceMessage.messageType === constant.typeFile ||
+        referenceMessage.messageType === constant.typeAudio
+      "
+    >
+      {{ referenceMessage?.messageAbstract }}
+    </span>
+  </div>
+  <div v-else :class="`${type} ${message.flow === 'in' && type + '-reverse'}`">
+    <p>{{ referenceMessage?.messageSender }}</p>
+    <span>{{ referenceMessage?.messageAbstract }}</span>
+  </div>
+</template>
+<script lang="ts">
+import { Message } from '../interface';
+import { defineComponent, reactive, toRefs, watch, watchEffect } from 'vue';
+import constant from '../../constant';
+const MessageReference = defineComponent({
+  props: {
+    message: {
+      type: Object,
+      default: {} as Message
+    },
+    referenceMessage: {
+      type: Object,
+      default: () => ({})
+    },
+    referenceForShow: {
+      type: Object,
+      default: () => ({})
+    },
+    face: {
+      type: Array,
+      default: () => []
+    },
+    url: {
+      type: String,
+      default: ''
+    },
+    allMessageID: {
+      type: String,
+      default: ''
+    },
+    type: {
+      type: String,
+      deafault: ''
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      message: {},
+      referenceMessage: {},
+      referenceForShow: {},
+      allMessageID: '',
+      face: [],
+      url: '',
+      type: ''
+    });
+    watchEffect(() => {
+      data.message = props.message;
+      data.referenceMessage = props.referenceMessage;
+      data.referenceForShow = props.referenceForShow;
+      data.face = props.face;
+      data.url = props.url;
+      data.allMessageID = props.allMessageID;
+      data.type = props.type;
+    });
+    watch(
+      () => props.referenceForShow,
+      () => {
+        data.referenceForShow = props.referenceForShow;
+      },
+      {
+        deep: true
+      }
+    );
+    return {
+      ...toRefs(data),
+      constant
+    };
+  }
+});
+export default MessageReference;
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.reference {
+  width: 100%;
+  display: flex;
+  flex-direction: inherit;
+  justify-content: center;
+  padding: 10Px;
+  line-height: 20Px;
+  .message-img,
+  .message-video-cover,
+  .message-videoimg {
+    max-width: min(calc(100vw - 230Px), 300Px);
+    max-height: min(calc(100vw - 230Px), 300Px);
+  }
+  .face-box {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    &-name {
+      padding-right: 5Px;
+    }
+    .text-img {
+      width: 20Px;
+      height: 20Px;
+    }
+  }
+  p {
+    font-family: PingFangSC-Regular;
+    font-weight: 400;
+    font-size: 12Px;
+    color: #999999;
+    letter-spacing: 0;
+    padding-right: 5Px;
+    max-width: 100Px;
+    box-sizing: border-box;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+  span {
+    line-height: 20Px;
+    font-family: PingFangSC-Regular;
+    font-weight: 400;
+    font-size: 12Px;
+    color: #999999;
+    letter-spacing: 0;
+    letter-spacing: 0;
+    word-wrap: break-word;
+    word-break: break-all;
+    display: inline-block;
+    p {
+      font-weight: 800;
+    }
+  }
+}
+.reply {
+  display: flex;
+  flex-direction: column;
+  align-self: start;
+  border-left: 2Px solid rgba(0, 110, 255, 0.499298);
+  &-reverse {
+    border-left: 2Px solid rgba(153, 153, 153, 0.3);
+  }
+  padding-left: 7Px;
+  margin-bottom: 5Px;
+  color: #999999;
+  font-size: 12Px;
+  line-height: 20Px;
+  min-width: 40Px;
+  .message-img,
+  .message-video-cover,
+  .message-videoimg {
+    max-width: min(calc(100vw - 180Px), 300Px);
+    max-height: min(calc(100vw - 180Px), 300Px);
+    object-fit: contain;
+    align-self: flex-start;
+  }
+  .face-box {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    .text-img {
+      width: 20Px;
+      height: 20Px;
+    }
+  }
+  p {
+    font-weight: 800;
+    max-width: min(calc(100vw - 180Px), 300Px);
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+}
+.message-reference-user {
+  display: flex;
+  flex-direction: row;
+  .nick {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+  .colon {
+    width: fit-content;
+  }
+}
+</style>

+ 64 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-revoked.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="revoke">
+    <label v-if="message.flow === 'in'">
+      {{ message.nick || message.from }}
+    </label>
+    <label v-else>{{ $t('TUIChat.您') }}</label>
+    <span>{{ $t('TUIChat.撤回了一条消息') }}</span>
+    <span class="edit" v-if="message.flow === 'out' && isEdit" @click="edit">
+      {{ $t('TUIChat.重新编辑') }}
+    </span>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    isEdit: {
+      type: Boolean,
+      default: () => false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      message: {},
+      isEdit: false
+    });
+
+    watchEffect(() => {
+      data.message = props.data;
+      data.isEdit = props.isEdit;
+    });
+
+    const edit = () => {
+      ctx.emit('edit', data.message);
+    };
+
+    return {
+      ...toRefs(data),
+      edit
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.revoke {
+  display: flex;
+  justify-content: center;
+  color: #999999;
+  width: 100%;
+  font-size: 14Px;
+  .edit {
+    padding: 0 5Px;
+    color: #006eff;
+  }
+}
+</style>

+ 143 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-system.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="message-system">
+    <ul class="list">
+      <li v-for="(item, index) in messageList" :key="index">
+        <template
+          v-if="
+            item.type === types.MSG_GRP_TIP ||
+            item.type === types.MSG_GRP_SYS_NOTICE
+          "
+        >
+          <i class="icon icon-system"></i>
+          <span>{{ translateGroupSystemNotice(item) }}</span>
+          <div class="btn-box" v-if="item?.payload?.operationType === 1">
+            <button
+              class="btn btn-default"
+              @click="handleApplication('Agree', item)"
+            >
+              接受
+            </button>
+            <button
+              class="btn btn-cancel"
+              @click="handleApplication('Reject', item)"
+            >
+              拒绝
+            </button>
+          </div>
+        </template>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+import { translateGroupSystemNotice } from '../utils/utils';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Array,
+      default: () => []
+    },
+    types: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      messageList: [],
+      types: {}
+    });
+
+    watchEffect(() => {
+      data.messageList = props.data;
+      data.types = props.types;
+    });
+
+    const handleApplication = (handleAction: string, message: any) => {
+      const options: any = {
+        handleAction,
+        message
+      };
+      ctx.emit('application', options);
+    };
+
+    return {
+      ...toRefs(data),
+      translateGroupSystemNotice,
+      handleApplication
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.list {
+  flex: 1;
+  height: 100%;
+  overflow-y: auto;
+  min-width: 600Px;
+  li {
+    display: flex;
+    align-items: center;
+    position: relative;
+    padding: 10Px 20Px;
+    font-weight: 400;
+    font-size: 14Px;
+    color: #000000;
+    letter-spacing: 0;
+    .icon {
+      margin-right: 10Px;
+    }
+    .message-label {
+      max-width: 50Px;
+    }
+    .btn-box {
+      padding: 0 12Px;
+    }
+  }
+}
+.icon {
+  display: inline-block;
+  width: 16Px;
+  height: 16Px;
+  &-warn {
+    border-radius: 50%;
+    background: coral;
+    color: #ffffff;
+    font-style: normal;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+.btn {
+  padding: 2Px 10Px;
+  margin-right: 12Px;
+  border-radius: 4Px;
+  border: none;
+  font-weight: 400;
+  font-size: 14Px;
+  color: #ffffff;
+  letter-spacing: 0;
+  text-align: center;
+  line-height: 20Px;
+  &:last-child {
+    margin-right: 0;
+  }
+  &-cancel {
+    border: 1Px solid #dddddd;
+    color: #666666;
+  }
+  &-default {
+    background: #006eff;
+    border: 1Px solid #006eff;
+  }
+  &:disabled {
+    opacity: 0.3;
+  }
+}
+</style>

+ 46 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-text.vue

@@ -0,0 +1,46 @@
+<template>
+  <template v-for="(item, index) in data.text" :key="index">
+    <span class="text-box" v-if="item.name === 'text'">{{ item.text }}</span>
+    <img class="text-img" v-else-if="item.name === 'img'" :src="item.src" />
+  </template>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {}
+    });
+
+    watchEffect(() => {
+      data.data = props.data;
+    });
+    return {
+      ...toRefs(data)
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.text-img {
+  width: 20Px;
+  height: 20Px;
+}
+.text-box {
+  white-space: pre-wrap;
+  font-size: inherit;
+  word-break: break-word;
+  font-size: 14Px;
+  text-size-adjust: none;
+}
+</style>

+ 138 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-timestamp.vue

@@ -0,0 +1,138 @@
+<template>
+  <div class="message-timestamp" v-show="timestampShowFlag">
+    {{ timestampShowContent }}
+  </div>
+</template>
+<script setup lang="ts">
+import { defineProps, toRefs, ref, watch } from 'vue';
+const props = defineProps({
+  currTime: {
+    type: Number,
+    default: 0
+  },
+  prevTime: {
+    type: Number,
+    default: 0
+  }
+});
+// eslint-disable-next-line no-unsafe-optional-chaining
+const { t } = (window as any)?.TUIKitTUICore?.config?.i18n?.useI18n();
+const { currTime, prevTime } = toRefs(props);
+const timestampShowFlag = ref(false);
+const timestampShowContent = ref('');
+
+const handleItemTime = (currTime: number, prevTime: number) => {
+  timestampShowFlag.value = false;
+  if (currTime <= 0) {
+    return '';
+  } else if (!prevTime || prevTime <= 0) {
+    timestampShowFlag.value = true;
+    return calculateTimestamp(currTime * 1000);
+  } else {
+    const minDiffToShow = 10 * 60; //10min 10*60s
+    const diff = currTime - prevTime; //s
+    if (diff >= minDiffToShow) {
+      timestampShowFlag.value = true;
+      return calculateTimestamp(currTime * 1000);
+    }
+  }
+  return '';
+};
+
+watch(
+  () => [currTime.value, prevTime.value],
+  (newVal: any, oldVal: any) => {
+    if (newVal?.toString() === oldVal?.toString()) {
+      return;
+    } else {
+      timestampShowContent.value = handleItemTime(
+        currTime.value,
+        prevTime.value
+      );
+    }
+  },
+  {
+    immediate: true
+  }
+);
+
+// 计算时间戳函数
+// calculate timestamp
+function calculateTimestamp(timestamp: number): string {
+  const todayZero = new Date().setHours(0, 0, 0, 0);
+  const thisYear = new Date(
+    new Date().getFullYear(),
+    0,
+    1,
+    0,
+    0,
+    0,
+    0
+  ).getTime();
+  const target = new Date(timestamp);
+
+  const oneDay = 24 * 60 * 60 * 1000;
+  const oneWeek = 7 * oneDay;
+  const oneYear = 365 * oneDay;
+
+  const diff = todayZero - target.getTime();
+
+  function formatNum(num: number): string {
+    return num < 10 ? '0' + num : num.toString();
+  }
+
+  if (diff <= 0) {
+    // today, only display hour:minute
+    return `${formatNum(target.getHours())}:${formatNum(target.getMinutes())}`;
+  } else if (diff <= oneDay) {
+    // yesterday, display yesterday:hour:minute
+    return `${t('time.昨天')} ${formatNum(target.getHours())}:${formatNum(
+      target.getMinutes()
+    )}`;
+  } else if (diff <= oneWeek - oneDay) {
+    // Within a week, display weekday hour:minute
+    const weekdays = [
+      '星期日',
+      '星期一',
+      '星期二',
+      '星期三',
+      '星期四',
+      '星期五',
+      '星期六'
+    ];
+    const weekday = weekdays[target.getDay()];
+    return `${t('time.' + weekday)} ${formatNum(target.getHours())}:${formatNum(
+      target.getMinutes()
+    )}`;
+  } else if (target.getTime() >= thisYear) {
+    // Over a week, within this year, display mouth/day hour:minute
+    return `${target.getMonth() + 1}/${target.getDate()} ${formatNum(
+      target.getHours()
+    )}:${formatNum(target.getMinutes())}`;
+  } else {
+    // Not within this year, display year/mouth/day hour:minute
+    return `${target.getFullYear()}/${
+      target.getMonth() + 1
+    }/${target.getDate()} ${formatNum(target.getHours())}:${formatNum(
+      target.getMinutes()
+    )}`;
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-timestamp {
+  margin: 0 auto;
+  color: #999999;
+  font-size: 12Px;
+  width: -webkit-fill-available;
+  overflow-wrap: anywhere;
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  align-items: center;
+  text-align: center;
+  padding: 0 20Px 30Px;
+}
+</style>

+ 47 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-tip.vue

@@ -0,0 +1,47 @@
+<template>
+  <div class="message-tip">
+    <span>{{ message?.text }}</span>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      message: {}
+    });
+
+    watchEffect(() => {
+      data.message = props.data;
+    });
+
+    return {
+      ...toRefs(data)
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-tip {
+  margin: 0 auto;
+  color: #999999;
+  font-size: 12Px;
+  width: -webkit-fill-available;
+  overflow-wrap: anywhere;
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  align-items: center;
+  text-align: center;
+}
+</style>

+ 264 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-tool.vue

@@ -0,0 +1,264 @@
+<template>
+  <div
+    class="dialog-item"
+    :class="env?.isH5 ? 'dialog-item-h5' : 'dialog-item-web'"
+  >
+    <MessageEmojiReact
+      v-if="env?.isH5 && needEmojiReact"
+      :message="message"
+      type="dropdown"
+      @handleCollapse="handleCollapse"
+    />
+    <ul
+      class="dialog-item-list"
+      :class="env?.isH5 ? 'dialog-item-list-h5' : 'dialog-item-list-web'"
+      v-show="showToolList"
+    >
+      <li
+        v-if="
+          (message.type === types.MSG_FILE ||
+            message.type === types.MSG_VIDEO ||
+            message.type === types.MSG_IMAGE) &&
+          !env.isH5
+        "
+        @click="openMessage(message)"
+      >
+        <i class="icon icon-msg-copy"></i>
+        <span>{{ $t('TUIChat.打开') }}</span>
+      </li>
+      <li
+        v-if="message.type === types.MSG_TEXT"
+        @click="handleMessage(message, constant.handleMessage.copy)"
+      >
+        <i class="icon icon-msg-copy"></i>
+        <span>{{ $t('TUIChat.复制') }}</span>
+      </li>
+      <li
+        v-if="message.status === 'success'"
+        @click="handleMessage(message, constant.handleMessage.forward)"
+      >
+        <i class="icon icon-msg-forward"></i>
+        <span>{{ $t('TUIChat.转发') }}</span>
+      </li>
+      <li
+        v-if="message.status === 'success'"
+        @click="handleMessage(message, constant.handleMessage.reference)"
+      >
+        <i class="icon icon-msg-quote"></i>
+        <span>{{ $t('TUIChat.引用') }}</span>
+      </li>
+      <li
+        v-if="message.status === 'success'"
+        @click="handleMessage(message, constant.handleMessage.reply)"
+      >
+        <i class="icon icon-msg-reply"></i>
+        <span>{{ $t('TUIChat.回复') }}</span>
+      </li>
+      <li
+        v-if="
+          message.flow === 'out' &&
+          message.status === 'success' &&
+          message.type !== types.MSG_CUSTOM
+        "
+        @click="handleMessage(message, constant.handleMessage.revoke)"
+      >
+        <i class="icon icon-msg-revoke"></i>
+        <span>{{ $t('TUIChat.撤回') }}</span>
+      </li>
+      <li
+        v-if="message.status === 'success'"
+        @click="handleMessage(message, constant.handleMessage.delete)"
+      >
+        <i class="icon icon-msg-del"></i>
+        <span>{{ $t('TUIChat.删除') }}</span>
+      </li>
+    </ul>
+  </div>
+</template>
+<script lang="ts">
+import {
+  defineComponent,
+  watch,
+  reactive,
+  toRefs,
+  ref,
+  watchEffect
+} from 'vue';
+import { Message } from '../interface';
+import TIM from '../../../../TUICore/tim';
+import { handleErrorPrompts } from '../../utils';
+import constant from '../../constant';
+import useClipboard from 'vue-clipboard3';
+import { useStore } from 'vuex';
+import MessageEmojiReact from './message-emoji-react.vue';
+export default defineComponent({
+  props: {
+    message: {
+      type: Object,
+      default: () => ({})
+    },
+    needEmojiReact: {
+      type: Boolean,
+      default: false
+    }
+  },
+  components: {
+    MessageEmojiReact
+  },
+  setup(props: any, ctx: any) {
+    const TUIServer = (window as any)?.TUIKitTUICore?.TUIServer?.TUIChat;
+    const VuexStore =
+      ((window as any)?.TUIKitTUICore?.isOfficial && useStore && useStore()) ||
+      {};
+    const data = reactive({
+      message: {} as Message,
+      show: false,
+      types: TIM.TYPES,
+      env: TUIServer.TUICore.TUIEnv,
+      showToolList: true,
+      needEmojiReact: false
+    });
+
+    watchEffect(() => {
+      data.needEmojiReact = props.needEmojiReact;
+    });
+
+    watch(
+      () => props.message,
+      () => {
+        data.message = props.message;
+      },
+      { deep: true, immediate: true }
+    );
+
+    const openMessage = (item: any) => {
+      let url = '';
+      switch (item.type) {
+        case data.types.MSG_FILE:
+          url = item.payload.fileUrl;
+          break;
+        case data.types.MSG_VIDEO:
+          url = item.payload.remoteVideoUrl;
+          break;
+        case data.types.MSG_IMAGE:
+          url = item.payload.imageInfoArray[0].url;
+          break;
+      }
+      window.open(url, '_blank');
+    };
+
+    const handleMessage = async (message: Message, type: string) => {
+      switch (type) {
+        case constant.handleMessage.revoke:
+          try {
+            await TUIServer.revokeMessage(message);
+            (window as any)?.TUIKitTUICore?.isOfficial &&
+              VuexStore?.commit &&
+              VuexStore?.commit('handleTask', 1);
+          } catch (error) {
+            handleErrorPrompts(error, data.env);
+          }
+          break;
+        case constant.handleMessage.copy:
+          try {
+            if (
+              message?.type === data.types.MSG_TEXT &&
+              message?.payload?.text
+            ) {
+              const { toClipboard } = useClipboard();
+              await toClipboard(message?.payload?.text);
+            }
+          } catch (error) {
+            handleErrorPrompts(error, data.env);
+          }
+          break;
+        case constant.handleMessage.delete:
+          await TUIServer.deleteMessage([message]);
+          break;
+        case constant.handleMessage.forward:
+          ctx.emit('handleMessage', message, constant.handleMessage.forward);
+          break;
+        case constant.handleMessage.reference:
+          ctx.emit('handleMessage', message, constant.handleMessage.reference);
+          break;
+        case constant.handleMessage.reply:
+          ctx.emit('handleMessage', message, constant.handleMessage.reply);
+          break;
+      }
+    };
+
+    const handleCollapse = (isCollapse: boolean) => {
+      if (!data?.env?.isH5) return;
+      data.showToolList = isCollapse;
+    };
+
+    return {
+      ...toRefs(data),
+      openMessage,
+      handleMessage,
+      constant,
+      handleCollapse
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.dialog-item {
+  background: #ffffff;
+  min-width: min-content;
+  max-width: 220Px;
+  width: 72Px;
+  height: fit-content;
+  word-break: keep-all;
+  top: 30Px;
+  border-radius: 8Px;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: baseline;
+  white-space: nowrap;
+  border: 1Px solid #e0e0e0;
+  &-web {
+    padding: 12Px 0;
+  }
+
+  &-list {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: baseline;
+    white-space: nowrap;
+    justify-content: space-around;
+    width: 100%;
+    &-h5 {
+      flex-wrap: nowrap;
+      margin: 10Px;
+      li {
+        padding: 0 5Px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        font-size: 8Px;
+        color: #4f4f4f;
+      }
+    }
+    &-web {
+      li:first-child {
+        margin-top: 0;
+      }
+
+      li {
+        padding: 4Px 12Px;
+        font-size: 12Px;
+        line-height: 17Px;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        span {
+          padding-left: 4Px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 350 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/components/message-video.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="message-video">
+    <div
+      class="message-video-box"
+      :class="[
+        !data.progress &&
+          data.message.status === 'success' &&
+          isH5 &&
+          'message-video-cover'
+      ]"
+      @click="toggleShow"
+      ref="skeleton"
+    >
+      <img
+        class="message-img"
+        v-if="(data.progress && poster) || (isH5 && poster)"
+        :class="[isWidth ? 'isWidth' : 'isHeight']"
+        :src="poster"
+      />
+      <video
+        class="message-img video-h5-uploading"
+        v-else-if="isH5"
+        :src="data.url + '#t=0.1'"
+        :poster="data.url"
+        preload="auto"
+        muted
+        ref="video"
+      ></video>
+      <video
+        class="message-img video-web"
+        v-else-if="!data.progress && !isH5"
+        :src="data.url"
+        controls
+        preload="metadata"
+        :poster="poster"
+        ref="video"
+      ></video>
+      <div class="progress" v-if="data.progress">
+        <progress :value="data.progress" max="1"></progress>
+      </div>
+    </div>
+    <div class="dialog-video" v-if="show && isH5" @click.self="toggleShow">
+      <header>
+        <i class="icon icon-close" @click.stop="toggleShow"></i>
+      </header>
+      <div
+        class="dialog-video-box"
+        :class="[isH5 ? 'dialog-video-h5' : '']"
+        @click.self="toggleShow"
+      >
+        <video
+          :class="[isWidth ? 'isWidth' : 'isHeight']"
+          :src="data.url"
+          controls
+          autoplay
+        ></video>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  watchEffect,
+  reactive,
+  toRefs,
+  computed,
+  nextTick,
+  ref,
+  watch
+} from 'vue';
+import { handleSkeletonSize } from '../utils/utils';
+
+export default defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    isH5: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      data: {},
+      show: false,
+      poster: '',
+      posterWidth: 0,
+      posterHeight: 0
+    });
+    const skeleton = ref();
+    const video = ref();
+    const isWidth = computed(() => {
+      // eslint-disable-next-line no-unsafe-optional-chaining
+      const { snapshotWidth = 0, snapshotHeight = 0 } = (data.data as any)
+        ?.message?.payload;
+      return snapshotWidth >= snapshotHeight;
+    });
+    const transparentPosterUrl =
+      'https://web.sdk.qcloud.com/im/assets/images/transparent.png';
+
+    const toggleShow = () => {
+      if (!(data.data as any).progress) {
+        data.show = !data.show;
+      }
+    };
+
+    // h5 部分浏览器(safari / wx)video标签 封面为空 在视频未上传完成前的封面展示需要单独进行处理截取
+    const getVideoBase64 = (url: string) => {
+      return new Promise(function (resolve, reject) {
+        let dataURL = '';
+        let video = document.createElement('video');
+        video.setAttribute('crossOrigin', 'anonymous'); //处理跨域
+        video.setAttribute('src', url);
+        video.setAttribute('preload', 'auto');
+        video.addEventListener(
+          'loadeddata',
+          function () {
+            let canvas = document.createElement('canvas'),
+              width = video.videoWidth, //canvas的尺寸和图片一样
+              height = video.videoHeight;
+            canvas.width = width;
+            canvas.height = height;
+            (canvas as any)
+              .getContext('2d')
+              .drawImage(video, 0, 0, width, height); //绘制canvas
+            dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
+            data.posterWidth = width;
+            data.posterHeight = height;
+            resolve(dataURL);
+          },
+          { once: true }
+        );
+      });
+    };
+
+    const handlePosterUrl = async (data: any) => {
+      if (!data) return '';
+      if (data.progress) {
+        return await getVideoBase64(data.url);
+      } else {
+        return (
+          (data.snapshotUrl !== transparentPosterUrl && data.snapshotUrl) ||
+          (data?.message?.payload?.snapshotUrl !== transparentPosterUrl &&
+            data?.message?.payload?.snapshotUrl) ||
+          (data?.message?.payload?.thumbUrl !== transparentPosterUrl &&
+            data?.message?.payload?.thumbUrl) ||
+          (await getVideoBase64(data.url))
+        );
+      }
+    };
+
+    watchEffect(async () => {
+      data.data = props.data;
+      if (!data.data) return;
+      data.poster = await handlePosterUrl(data.data);
+      nextTick(async () => {
+        const containerWidth =
+          document.getElementById('messageEle')?.clientWidth || 0;
+        const max = props.isH5 ? Math.min(containerWidth - 172, 300) : 300;
+        let size;
+        if (!(data.data as any).progress) {
+          let {
+            snapshotWidth = 0,
+            snapshotHeight = 0,
+            snapshotUrl
+          } = data.data as any;
+          if (snapshotWidth === 0 || snapshotHeight === 0) return;
+          if (snapshotUrl === transparentPosterUrl) {
+            snapshotWidth = data.posterWidth;
+            snapshotHeight = data.posterHeight;
+          }
+          size = handleSkeletonSize(snapshotWidth, snapshotHeight, max, max);
+          skeleton?.value?.style &&
+            (skeleton.value.style.width = `${size.width}Px`);
+          skeleton?.value?.style &&
+            (skeleton.value.style.height = `${size.height}Px`);
+          if (!props.isH5) {
+            video?.value?.style &&
+              (video.value.style.width = `${size.width}Px`);
+            video?.value?.style &&
+              (video.value.style.height = `${size.height}Px`);
+          }
+        } else {
+          ctx.emit('uploading');
+        }
+      });
+    });
+
+    watch(
+      () => (data.data as any)?.progress,
+      (newVal, oldVal) => {
+        if (!newVal && oldVal) {
+          ctx.emit('uploading');
+        }
+      }
+    );
+
+    return {
+      ...toRefs(data),
+      toggleShow,
+      isWidth,
+      getVideoBase64,
+      handlePosterUrl,
+      skeleton,
+      video
+    };
+  }
+});
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-video {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  overflow: hidden;
+  &-box {
+    max-width: min(calc(100vw - 180Px), 300Px);
+    video {
+      max-width: min(calc(100vw - 180Px), 300Px);
+      max-height: min(calc(100vw - 180Px), 300Px);
+      width: inherit;
+      height: inherit;
+      border-radius: 10Px;
+    }
+    img {
+      max-width: min(calc(100vw - 180Px), 300Px);
+      max-height: min(calc(100vw - 180Px), 300Px);
+      width: inherit;
+      height: inherit;
+      border-radius: 10Px;
+    }
+    img[src=''],
+    img:not([src]) {
+      opacity: 0;
+    }
+  }
+  &-cover {
+    display: inline-block;
+    position: relative;
+    &::before {
+      position: absolute;
+      z-index: 1;
+      content: '';
+      width: 0Px;
+      height: 0Px;
+      border: 10Px solid transparent;
+      border-left: 15Px solid #ffffff;
+      top: 0;
+      left: 0;
+      bottom: 0;
+      right: 0;
+      margin: auto;
+      transform: translate(5Px, 0Px);
+    }
+    video {
+      max-width: min(calc(100vw - 180Px), 300Px);
+      max-height: min(calc(100vw - 180Px), 300Px);
+      width: inherit;
+      height: inherit;
+      border-radius: 10Px;
+    }
+  }
+  .progress {
+    position: absolute;
+    box-sizing: border-box;
+    width: 100%;
+    height: 100%;
+    padding: 0 20Px;
+    border-radius: 10Px;
+    left: 0;
+    top: 0;
+    background: rgba(#000000, 0.5);
+    display: flex;
+    align-items: center;
+    flex: 1;
+    progress {
+      color: #006eff;
+      appearance: none;
+      border-radius: 0.25rem;
+      background: rgba(#ffffff, 1);
+      width: 100%;
+      height: 0.5rem;
+      &::-webkit-progress-value {
+        background-color: #006eff;
+        border-radius: 0.25rem;
+      }
+      &::-webkit-progress-bar {
+        border-radius: 0.25rem;
+        background: rgba(#ffffff, 1);
+      }
+      &::-moz-progress-bar {
+        color: #006eff;
+        background: #006eff;
+        border-radius: 0.25rem;
+      }
+    }
+  }
+}
+.dialog-video {
+  position: fixed;
+  z-index: 12;
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  left: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  header {
+    display: flex;
+    justify-content: flex-end;
+    background: rgba(0, 0, 0, 0.49);
+    width: 100%;
+    box-sizing: border-box;
+    padding: 10Px 10Px;
+  }
+  &-box {
+    display: flex;
+    flex: 1;
+    max-height: 100%;
+    padding: 6rem;
+    box-sizing: border-box;
+    justify-content: center;
+    align-items: center;
+    video {
+      max-width: 100%;
+      max-height: 100%;
+    }
+  }
+}
+.dialog-video-h5 {
+  width: 100%;
+  height: 100%;
+  background: #000000;
+  padding: 30Px 0;
+}
+
+.isWidth {
+  width: 100%;
+}
+.isHeight {
+  height: 100%;
+}
+</style>

+ 1661 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/dist/server.js

@@ -0,0 +1,1661 @@
+"use strict";
+var __extends = (this && this.__extends) || (function () {
+    var extendStatics = function (d, b) {
+        extendStatics = Object.setPrototypeOf ||
+            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+        return extendStatics(d, b);
+    };
+    return function (d, b) {
+        extendStatics(d, b);
+        function __() { this.constructor = d; }
+        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+    };
+})();
+var __assign = (this && this.__assign) || function () {
+    __assign = Object.assign || function(t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+            s = arguments[i];
+            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+                t[p] = s[p];
+        }
+        return t;
+    };
+    return __assign.apply(this, arguments);
+};
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var __generator = (this && this.__generator) || function (thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+};
+var __spreadArrays = (this && this.__spreadArrays) || function () {
+    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
+    for (var r = Array(s), k = 0, i = 0; i < il; i++)
+        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
+            r[k] = a[j];
+    return r;
+};
+exports.__esModule = true;
+var TUIPlugin_1 = require("../../../TUIPlugin");
+var IComponentServer_1 = require("../IComponentServer");
+var utils_1 = require("./utils/utils");
+/**
+ * class TUIChatServer
+ *
+ * TUIChat 逻辑主体
+ */
+var TUIChatServer = /** @class */ (function (_super) {
+    __extends(TUIChatServer, _super);
+    function TUIChatServer(TUICore) {
+        var _this = _super.call(this) || this;
+        _this.currentStore = {};
+        _this.TUICore = TUICore;
+        _this.bindTIMEvent();
+        _this.store = TUICore.setComponentStore("TUIChat", {}, _this.updateStore.bind(_this));
+        return _this;
+    }
+    /**
+     * 组件销毁
+     * destroy
+     */
+    TUIChatServer.prototype.destroyed = function () {
+        this.unbindTIMEvent();
+    };
+    /**
+     * 数据监听回调
+     * data listener callback
+     *
+     * @param {any} newValue 新数据
+     * @param {any} oldValue 旧数据
+     *
+     */
+    TUIChatServer.prototype.updateStore = function (newValue, oldValue) {
+        Object.assign(this.currentStore, newValue);
+        if (!newValue.conversation.conversationID) {
+            this.currentStore.messageList = [];
+            return;
+        }
+        if (newValue.conversation.conversationID &&
+            newValue.conversation.conversationID !==
+                oldValue.conversation.conversationID) {
+            this.render(newValue.conversation);
+        }
+    };
+    TUIChatServer.prototype.render = function (conversation) {
+        var _this = this;
+        var _a, _b, _c;
+        var len = 15;
+        this.currentStore.isFirstRender = true;
+        this.currentStore.messageList = [];
+        this.currentStore.readSet.clear();
+        this.getMessageList({
+            conversationID: conversation.conversationID,
+            count: len
+        });
+        if (conversation.type === this.TUICore.TIM.TYPES.CONV_GROUP) {
+            this.currentStore.userInfo.isGroup = true;
+            var options = {
+                groupID: conversation.groupProfile.groupID,
+                userIDList: [conversation.groupProfile.selfInfo.userID]
+            };
+            this.getGroupProfile({ groupID: conversation.groupProfile.groupID });
+            this.getGroupMemberProfile(options).then(function (res) {
+                var memberList = res.data.memberList;
+                var selfInfo = memberList[0];
+                _this.currentStore.selfInfo = selfInfo;
+            });
+            (_c = (_b = (_a = this === null || this === void 0 ? void 0 : this.TUICore) === null || _a === void 0 ? void 0 : _a.TUIServer) === null || _b === void 0 ? void 0 : _b.TUIGroup) === null || _c === void 0 ? void 0 : _c.getGroupMemberList({
+                groupID: conversation.groupProfile.groupID,
+                count: 100,
+                offset: 0
+            }).then(function (res) {
+                var _a;
+                _this.currentStore.allMemberList = (_a = res.data) === null || _a === void 0 ? void 0 : _a.memberList;
+            });
+        }
+        else {
+            this.currentStore.userInfo.isGroup = false;
+            this.currentStore.userInfo.list = [conversation === null || conversation === void 0 ? void 0 : conversation.userProfile];
+        }
+    };
+    /**
+     * /////////////////////////////////////////////////////////////////////////////////
+     * //
+     * //                                    TIM 事件监听注册接口
+     * //                        TIM Event listener registration interface
+     * //
+     * /////////////////////////////////////////////////////////////////////////////////
+     */
+    TUIChatServer.prototype.bindTIMEvent = function () {
+        this.TUICore.tim.on(this.TUICore.TIM.EVENT.MESSAGE_RECEIVED, this.handleMessageReceived, this);
+        this.TUICore.tim.on(this.TUICore.TIM.EVENT.MESSAGE_MODIFIED, this.handleMessageModified, this);
+        this.TUICore.tim.on(this.TUICore.TIM.EVENT.MESSAGE_REVOKED, this.handleMessageRevoked, this);
+        this.TUICore.tim.on(this.TUICore.TIM.EVENT.MESSAGE_READ_BY_PEER, this.handleMessageReadByPeer, this);
+        this.TUICore.tim.on(this.TUICore.TIM.EVENT.GROUP_LIST_UPDATED, this.handleGroupListUpdated, this);
+        this.TUICore.tim.on(this.TUICore.TIM.EVENT.MESSAGE_READ_RECEIPT_RECEIVED, this.handleMessageReadReceiptReceived, this);
+    };
+    TUIChatServer.prototype.unbindTIMEvent = function () {
+        this.TUICore.tim.off(this.TUICore.TIM.EVENT.MESSAGE_RECEIVED, this.handleMessageReceived);
+        this.TUICore.tim.off(this.TUICore.TIM.EVENT.MESSAGE_MODIFIED, this.handleMessageModified);
+        this.TUICore.tim.off(this.TUICore.TIM.EVENT.MESSAGE_REVOKED, this.handleMessageRevoked);
+        this.TUICore.tim.off(this.TUICore.TIM.EVENT.MESSAGE_READ_BY_PEER, this.handleMessageReadByPeer);
+        this.TUICore.tim.off(this.TUICore.TIM.EVENT.GROUP_LIST_UPDATED, this.handleGroupListUpdated);
+        this.TUICore.tim.off(this.TUICore.TIM.EVENT.MESSAGE_READ_RECEIPT_RECEIVED, this.handleMessageReadReceiptReceived);
+    };
+    TUIChatServer.prototype.handleMessageReceived = function (event) {
+        var _this = this;
+        var _a;
+        (_a = event === null || event === void 0 ? void 0 : event.data) === null || _a === void 0 ? void 0 : _a.forEach(function (message) {
+            var _a, _b;
+            if ((message === null || message === void 0 ? void 0 : message.conversationID) === ((_b = (_a = _this === null || _this === void 0 ? void 0 : _this.store) === null || _a === void 0 ? void 0 : _a.conversation) === null || _b === void 0 ? void 0 : _b.conversationID)) {
+                _this.currentStore.messageList = __spreadArrays(_this.currentStore.messageList, [
+                    message,
+                ]);
+            }
+            TUIPlugin_1.TUINotification.getInstance().notify(message);
+        });
+    };
+    TUIChatServer.prototype.handleMessageModified = function (event) {
+        var middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+    };
+    TUIChatServer.prototype.handleMessageRevoked = function (event) {
+        var middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+    };
+    TUIChatServer.prototype.handleMessageReadByPeer = function (event) {
+        var middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+    };
+    TUIChatServer.prototype.handleGroupListUpdated = function (event) {
+        var _this = this;
+        event === null || event === void 0 ? void 0 : event.data.map(function (item) {
+            var _a, _b, _c;
+            if ((item === null || item === void 0 ? void 0 : item.groupID) === ((_c = (_b = (_a = _this === null || _this === void 0 ? void 0 : _this.store) === null || _a === void 0 ? void 0 : _a.conversation) === null || _b === void 0 ? void 0 : _b.groupProfile) === null || _c === void 0 ? void 0 : _c.groupID)) {
+                _this.store.conversation.groupProfile = item;
+                _this.currentStore.conversation = {};
+                _this.currentStore.conversation = _this.store.conversation;
+            }
+            return item;
+        });
+    };
+    TUIChatServer.prototype.handleMessageReadReceiptReceived = function (event) {
+        var middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+    };
+    /**
+     * /////////////////////////////////////////////////////////////////////////////////
+     * //
+     * //                                 处理 TIM 接口参数及回调
+     * //                     Handling TIM interface parameters and callbacks
+     * //
+     * /////////////////////////////////////////////////////////////////////////////////
+     */
+    /**
+     * 创建消息生成参数
+     * Create message generation parameters
+     *
+     * @param {Object} content 消息体
+     * @param {String} type 消息类型 text: 文本类型 file: 文件类型 face: 表情 location: 地址 custom: 自定义 merger: 合并 forward: 转发
+     * @param {Callback} callback 回调函数
+     * @param {any} to 发送的对象
+     * @returns {options} 消息参数
+     */
+    TUIChatServer.prototype.handleMessageOptions = function (content, type, callback, to) {
+        var _a, _b, _c, _d, _e, _f;
+        var options = {
+            to: "",
+            conversationType: (to === null || to === void 0 ? void 0 : to.type) || this.store.conversation.type,
+            payload: content,
+            needReadReceipt: this.currentStore.needReadReceipt
+        };
+        if (this.currentStore.needTyping) {
+            options.cloudCustomData = {
+                messageFeature: {
+                    needTyping: 1,
+                    version: 1
+                }
+            };
+            options.cloudCustomData = JSON.stringify(options.cloudCustomData);
+        }
+        if (type === "file" && callback) {
+            options.onProgress = callback;
+        }
+        switch (options.conversationType) {
+            case this.TUICore.TIM.TYPES.CONV_C2C:
+                options.to =
+                    ((_a = to === null || to === void 0 ? void 0 : to.userProfile) === null || _a === void 0 ? void 0 : _a.userID) || ((_c = (_b = this.store.conversation) === null || _b === void 0 ? void 0 : _b.userProfile) === null || _c === void 0 ? void 0 : _c.userID) ||
+                        "";
+                break;
+            case this.TUICore.TIM.TYPES.CONV_GROUP:
+                options.to =
+                    ((_d = to === null || to === void 0 ? void 0 : to.groupProfile) === null || _d === void 0 ? void 0 : _d.groupID) || ((_f = (_e = this.store.conversation) === null || _e === void 0 ? void 0 : _e.groupProfile) === null || _f === void 0 ? void 0 : _f.groupID) ||
+                        "";
+                break;
+            default:
+                break;
+        }
+        return options;
+    };
+    /**
+     * 处理异步函数
+     * Handling asynchronous functions
+     *
+     * @param {callback} callback 回调函数
+     * @returns {Promise} 返回异步函数
+     */
+    TUIChatServer.prototype.handlePromiseCallback = function (callback) {
+        var _this = this;
+        return new Promise(function (resolve, reject) {
+            var config = {
+                TUIName: "TUIChat",
+                callback: function () {
+                    callback && callback(resolve, reject);
+                }
+            };
+            _this.TUICore.setAwaitFunc(config.TUIName, config.callback);
+        });
+    };
+    /**
+     * 重试异步函数
+     * Retry asynchronous functions
+     * 默认执行一次,之后按时间间隔列表重复执行直到成功,重复次数完毕后仍失败则失败
+     * Execute once by default, and then repeat it according to the time interval list until it succeeds.
+     * If it still fails after the number of repetitions is complete, it will reject.
+     *
+     * @param {callback} callback 回调函数/callback function
+     * @param {Array<number>} intervalList 间隔时间列表/interval list
+     * @param {callback} retryBreakFn 强制重复结束条件函数/break retry function
+     * @returns {Promise} 返回异步函数/return
+     */
+    TUIChatServer.prototype.handlePromiseCallbackRetry = function (callback, intervalList, retryBreakFn) {
+        if (intervalList === void 0) { intervalList = []; }
+        if (retryBreakFn === void 0) { retryBreakFn = function () {
+            return false;
+        }; }
+        return new Promise(function (resolve, reject) {
+            var times = 0;
+            function tryFn() {
+                times++;
+                callback()
+                    .then(resolve)["catch"](function (error) {
+                    if (times > intervalList.length ||
+                        (retryBreakFn && retryBreakFn(error))) {
+                        reject(error);
+                        return;
+                    }
+                    setTimeout(tryFn, intervalList[times - 1]);
+                });
+            }
+            tryFn();
+        });
+    };
+    /**
+     * 文件上传进度函数处理
+     * File upload progress function processing
+     *
+     * @param {number} progress 文件上传进度 1表示完成
+     * @param {message} message 文件消息
+     */
+    TUIChatServer.prototype.handleUploadProgress = function (progress, message) {
+        this.currentStore.messageList.map(function (item) {
+            if (item.ID === message.ID) {
+                item.progress = progress;
+            }
+            return item;
+        });
+    };
+    /**
+     * /////////////////////////////////////////////////////////////////////////////////
+     * //
+     * //                                 TIM 方法
+     * //                               TIM methods
+     * //
+     * /////////////////////////////////////////////////////////////////////////////////
+     */
+    /**
+     * 发送表情消息
+     * Send face messages
+     *
+     * @param {Object} data 消息内容/message content
+     * @param {Number} data.index 表情索引/face index
+     * @param {String} data.data 额外数据/extra data
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendFaceMessage = function (data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message, imResponse_1, error_1, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions(data, "face");
+                        message = this.TUICore.tim.createFaceMessage(options);
+                        this.currentStore.messageList.push(message);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message)];
+                    case 1:
+                        imResponse_1 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_1.data.message.ID) {
+                                return imResponse_1.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_1);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_1 = _a.sent();
+                        reject(error_1);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送图片消息
+     * Send image message
+     *
+     * @param {Image} image 图片文件/image
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendImageMessage = function (image) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message_1, imResponse_2, error_2, middleData;
+            var _this = this;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions({ file: image }, "file", function (progress) {
+                            _this.handleUploadProgress(progress, message_1);
+                        });
+                        message_1 = this.TUICore.tim.createImageMessage(options);
+                        message_1.progress = 0.01;
+                        this.currentStore.messageList.push(message_1);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message_1)];
+                    case 1:
+                        imResponse_2 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_2.data.message.ID) {
+                                return imResponse_2.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_2);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_2 = _a.sent();
+                        reject(error_2);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送视频消息
+     * Send video message
+     *
+     * @param {Video} video 视频文件/video
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendVideoMessage = function (video) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message_2, imResponse_3, error_3, middleData;
+            var _this = this;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions({ file: video }, "file", function (progress) {
+                            _this.handleUploadProgress(progress, message_2);
+                        });
+                        message_2 = this.TUICore.tim.createVideoMessage(options);
+                        message_2.progress = 0.01;
+                        this.currentStore.messageList.push(message_2);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message_2)];
+                    case 1:
+                        imResponse_3 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_3.data.message.ID) {
+                                return imResponse_3.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_3);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_3 = _a.sent();
+                        reject(error_3);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送文件消息
+     * Send file message
+     *
+     * @param {File} file 文件/file
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendFileMessage = function (file) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message_3, imResponse_4, error_4, middleData;
+            var _this = this;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions({ file: file }, "file", function (progress) {
+                            _this.handleUploadProgress(progress, message_3);
+                        });
+                        message_3 = this.TUICore.tim.createFileMessage(options);
+                        message_3.progress = 0.01;
+                        this.currentStore.messageList.push(message_3);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message_3)];
+                    case 1:
+                        imResponse_4 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_4.data.message.ID) {
+                                return imResponse_4.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_4);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_4 = _a.sent();
+                        reject(error_4);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送自定义消息
+     * Send Custom message
+     *
+     * @param {Object} data 消息内容/message content
+     * @param {String} data.data 自定义消息的数据字段/custom message data fields
+     * @param {String} data.description 自定义消息的说明字段/custom message description fields
+     * @param {String} data.extension 自定义消息的扩展字段/custom message extension fields
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendCustomMessage = function (data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message, imResponse_5, error_5, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        data.data = JSON.stringify(data.data);
+                        options = this.handleMessageOptions(data, "custom");
+                        message = this.TUICore.tim.createCustomMessage(options);
+                        this.currentStore.messageList.push(message);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message)];
+                    case 1:
+                        imResponse_5 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_5.data.message.ID) {
+                                return imResponse_5.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_5);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_5 = _a.sent();
+                        reject(error_5);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送地理位置消息
+     * Send location message
+     *
+     * @param {Object} data 消息内容/message content
+     * @param {String} data.description 地理位置描述信息/geographic descriptive information
+     * @param {Number} data.longitude 经度/longitude
+     * @param {Number} data.latitude 纬度/latitude
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendLocationMessage = function (data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message, imResponse, error_6, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions(data, "location");
+                        message = this.TUICore.tim.createLocationMessage(options);
+                        this.currentStore.messageList.push(message);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message)];
+                    case 1:
+                        imResponse = _a.sent();
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_6 = _a.sent();
+                        reject(error_6);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 转发消息
+     * forward message
+     *
+     * @param {message} message 消息实例/message
+     * @param {any} to 转发的对象/forward to
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.forwardMessage = function (message, to) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, imMessage, imResponse, error_7, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions(message, "forward", {}, to);
+                        imMessage = this.TUICore.tim.createForwardMessage(options);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(imMessage)];
+                    case 1:
+                        imResponse = _a.sent();
+                        if (this.store.conversation.conversationID ===
+                            imResponse.data.message.conversationID) {
+                            this.currentStore.messageList.push(imResponse.data.message);
+                        }
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_7 = _a.sent();
+                        reject(error_7);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送消息已读回执
+     * Send message read receipt
+     *
+     * @param {Array} messageList 同一个 C2C 或 GROUP 会话的消息列表,最大长度为30/A list of messages for the same C2C or GROUP conversation, with a maximum length of 30
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendMessageReadReceipt = function (messageList) {
+        return __awaiter(this, void 0, void 0, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_8;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.sendMessageReadReceipt(messageList)];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_8 = _a.sent();
+                                    reject(error_8);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 拉取已读回执列表
+     * Pull read receipt list
+     *
+     * @param {Array} messageList 同一群会话的消息列表/The message list of the same group of the conversation
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getMessageReadReceiptList = function (messageList) {
+        return __awaiter(this, void 0, void 0, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_9;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.getMessageReadReceiptList(messageList)];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_9 = _a.sent();
+                                    reject(error_9);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * /////////////////////////////////////////////////////////////////////////////////
+     * //
+     * //                                 对外方法
+     * //
+     * /////////////////////////////////////////////////////////////////////////////////
+     */
+    /**
+     * 获取 messageList
+     * get messagelist
+     *
+     * @param {any} options 获取 messageList 参数/messageList options
+     * @param {Boolean} history  是否获取历史消息/Whether to get historical information
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getMessageList = function (options, history) {
+        return __awaiter(this, void 0, void 0, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_10;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 4, , 5]);
+                                    return [4 /*yield*/, this.TUICore.tim.getMessageList(options)];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    console.warn(imResponse.data.messageList);
+                                    if (!imResponse.data.messageList.length) return [3 /*break*/, 3];
+                                    return [4 /*yield*/, this.getMessageReadReceiptList(imResponse.data.messageList)];
+                                case 2:
+                                    _a.sent();
+                                    _a.label = 3;
+                                case 3:
+                                    if (!history) {
+                                        this.currentStore.messageList = imResponse.data.messageList;
+                                    }
+                                    else {
+                                        this.currentStore.messageList = __spreadArrays(imResponse.data.messageList, this.currentStore.messageList);
+                                    }
+                                    this.currentStore.nextReqMessageID = imResponse.data.nextReqMessageID;
+                                    this.currentStore.isCompleted = imResponse.data.isCompleted;
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 5];
+                                case 4:
+                                    error_10 = _a.sent();
+                                    reject(error_10);
+                                    return [3 /*break*/, 5];
+                                case 5: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 获取历史消息
+     * get history messagelist
+     *
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getHistoryMessageList = function () {
+        return __awaiter(this, void 0, void 0, function () {
+            var options;
+            return __generator(this, function (_a) {
+                options = {
+                    conversationID: this.currentStore.conversation.conversationID,
+                    nextReqMessageID: this.currentStore.nextReqMessageID,
+                    count: 15
+                };
+                if (!this.currentStore.isCompleted) {
+                    this.getMessageList(options, true);
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * 发送文本消息
+     * send text message
+     *
+     * @param {any} text 发送的消息/text message
+     * @param {object} data 被引用消息的内容/The content of the quoted message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendTextMessage = function (text, data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, cloudCustomDataObj, cloudCustomData, secondOptions, message, imResponse_6, error_11, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions({ text: text }, "text");
+                        cloudCustomDataObj = {};
+                        if (options.cloudCustomData) {
+                            try {
+                                cloudCustomDataObj = utils_1.JSONToObject(options.cloudCustomData);
+                            }
+                            catch (_b) {
+                                cloudCustomDataObj = {};
+                            }
+                        }
+                        cloudCustomData = JSON.stringify(data);
+                        secondOptions = Object.assign(options, __assign({ cloudCustomData: cloudCustomData }, cloudCustomDataObj));
+                        message = this.TUICore.tim.createTextMessage(secondOptions);
+                        this.currentStore.messageList.push(message);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message)];
+                    case 1:
+                        imResponse_6 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_6.data.message.ID) {
+                                return imResponse_6.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_6);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_11 = _a.sent();
+                        reject(error_11);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送【对方正在输入中】在线自定义消息
+     * send typing online custom message
+     *
+     * @param {Object} data 消息内容/message content
+     * @param {String} data.data 自定义消息的数据字段/custom message data field
+     * @param {String} data.description 自定义消息的说明字段/custom message description field
+     * @param {String} data.extension 自定义消息的扩展字段/custom message extension field
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendTypingMessage = function (data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message, imResponse, error_12, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        data.data = JSON.stringify(data.data);
+                        options = this.handleMessageOptions(data, "custom");
+                        message = this.TUICore.tim.createCustomMessage(options);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message, {
+                                onlineUserOnly: true
+                            })];
+                    case 1:
+                        imResponse = _a.sent();
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_12 = _a.sent();
+                        reject(error_12);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送@ 提醒功能的文本消息
+     * Send @ Reminder text message
+     *
+     * @param {any} data 消息内容/message content
+     * @param {String} data.text 文本消息/text message
+     * @param {Array} data.atUserList 需要 @ 的用户列表,如果需要 @ALL,请传入 TIM.TYPES.MSG_AT_ALL / List of users who need @, if you need @ALL, please pass in TIM.TYPES.MSG_AT_ALL
+     * @returns {message}
+     *
+     * - 注:此接口仅用于群聊/This interface is only used for group chat
+     */
+    TUIChatServer.prototype.sendTextAtMessage = function (data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message, imResponse_7, error_13, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions(data, "text");
+                        message = this.TUICore.tim.createTextAtMessage(options);
+                        this.currentStore.messageList.push(message);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message)];
+                    case 1:
+                        imResponse_7 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_7.data.message.ID) {
+                                return imResponse_7.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_7);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_13 = _a.sent();
+                        reject(error_13);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 发送合并消息
+     * send merger message
+     *
+     * @param {Object} data 消息内容/message content
+     * @param {Array.<Message>} data.messageList 合并的消息列表/merger message list
+     * @param {String} data.title 合并的标题/merger title
+     * @param {String} data.abstractList 摘要列表,不同的消息类型可以设置不同的摘要信息/Summary list, different message types can set different summary information
+     * @param {String} data.compatibleText 兼容文本/ompatible text
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.sendMergerMessage = function (data) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var options, message, imResponse_8, error_14, middleData;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        options = this.handleMessageOptions(data, "merger");
+                        message = this.TUICore.tim.createMergerMessage(options);
+                        this.currentStore.messageList.push(message);
+                        return [4 /*yield*/, this.TUICore.tim.sendMessage(message)];
+                    case 1:
+                        imResponse_8 = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.map(function (item) {
+                            if (item.ID === imResponse_8.data.message.ID) {
+                                return imResponse_8.data.message;
+                            }
+                            return item;
+                        });
+                        resolve(imResponse_8);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_14 = _a.sent();
+                        reject(error_14);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 消息撤回
+     * revoke message
+     *
+     * @param {message} message 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.revokeMessage = function (message) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, cloudCustomData, error_15, middleData;
+            var _a;
+            return __generator(this, function (_b) {
+                switch (_b.label) {
+                    case 0:
+                        _b.trys.push([0, 4, , 5]);
+                        return [4 /*yield*/, this.TUICore.tim.revokeMessage(message)];
+                    case 1:
+                        imResponse = _b.sent();
+                        cloudCustomData = utils_1.JSONToObject(message === null || message === void 0 ? void 0 : message.cloudCustomData);
+                        if (!((_a = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReply) === null || _a === void 0 ? void 0 : _a.messageRootID)) return [3 /*break*/, 3];
+                        return [4 /*yield*/, this.revokeReplyMessage(message)];
+                    case 2:
+                        _b.sent();
+                        _b.label = 3;
+                    case 3:
+                        resolve(imResponse);
+                        return [3 /*break*/, 5];
+                    case 4:
+                        error_15 = _b.sent();
+                        reject(error_15);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 5];
+                    case 5: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 重发消息
+     * resend message
+     *
+     * @param {message} message 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.resendMessage = function (message) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, error_16;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.resendMessage(message)];
+                    case 1:
+                        imResponse = _a.sent();
+                        this.currentStore.messageList = this.currentStore.messageList.filter(function (item) { return item.ID !== message.ID; });
+                        this.currentStore.messageList.push(imResponse.data.message);
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_16 = _a.sent();
+                        reject(error_16);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 删除消息
+     * delete message
+     *
+     * @param {Array.<message>} messages 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.deleteMessage = function (messages) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, middleData, error_17;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.deleteMessage(messages)];
+                    case 1:
+                        imResponse = _a.sent();
+                        resolve(imResponse);
+                        middleData = this.currentStore.messageList;
+                        this.currentStore.messageList = [];
+                        this.currentStore.messageList = middleData;
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_17 = _a.sent();
+                        reject(error_17);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 变更消息
+     * modify message
+     *
+     * @param {Array.<message>} message 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.modifyMessage = function (message) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, error_18, code, data;
+            var _a, _b;
+            return __generator(this, function (_c) {
+                switch (_c.label) {
+                    case 0:
+                        _c.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.modifyMessage(message)];
+                    case 1:
+                        imResponse = _c.sent();
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_18 = _c.sent();
+                        code = (_a = error_18) === null || _a === void 0 ? void 0 : _a.code;
+                        data = (_b = error_18) === null || _b === void 0 ? void 0 : _b.data;
+                        if (code === 2480) {
+                            console.warn("MODIFY_MESSAGE_ERROR", "修改消息发生冲突,data.message 是最新的消息", "data.message:", data === null || data === void 0 ? void 0 : data.message);
+                        }
+                        else if (code === 2481) {
+                            console.warn("MODIFY_MESSAGE_ERROR", "不支持修改直播群消息");
+                        }
+                        else if (code === 20026) {
+                            console.warn("MODIFY_MESSAGE_ERROR", "消息不存在");
+                        }
+                        reject(error_18);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 回复消息
+     * reply message
+     * @param {Array.<message>} message 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.replyMessage = function (message, messageRoot) {
+        var _this = this;
+        var replyFunction = function () {
+            return _this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                var repliesObject, cloudCustomData, messageRootID_1, rootCloudCustomData, imResponse, error_19;
+                var _a, _b, _c, _d, _e, _f;
+                return __generator(this, function (_g) {
+                    switch (_g.label) {
+                        case 0:
+                            _g.trys.push([0, 3, , 4]);
+                            repliesObject = {
+                                messageAbstract: (_a = message === null || message === void 0 ? void 0 : message.payload) === null || _a === void 0 ? void 0 : _a.text,
+                                messageSender: message === null || message === void 0 ? void 0 : message.from,
+                                messageID: message === null || message === void 0 ? void 0 : message.ID,
+                                messageType: message === null || message === void 0 ? void 0 : message.type,
+                                messageTime: message === null || message === void 0 ? void 0 : message.time,
+                                messageSequence: message === null || message === void 0 ? void 0 : message.sequence,
+                                version: 1
+                            };
+                            if (!!messageRoot) return [3 /*break*/, 2];
+                            cloudCustomData = utils_1.JSONToObject(message === null || message === void 0 ? void 0 : message.cloudCustomData);
+                            messageRootID_1 = (_b = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReply) === null || _b === void 0 ? void 0 : _b.messageRootID;
+                            return [4 /*yield*/, ((_d = (_c = this === null || this === void 0 ? void 0 : this.currentStore) === null || _c === void 0 ? void 0 : _c.messageList) === null || _d === void 0 ? void 0 : _d.find(function (item) { return (item === null || item === void 0 ? void 0 : item.ID) === messageRootID_1; }))];
+                        case 1:
+                            messageRoot =
+                                (_g.sent()) || this.findMessage(messageRootID_1);
+                            _g.label = 2;
+                        case 2:
+                            rootCloudCustomData = (messageRoot === null || messageRoot === void 0 ? void 0 : messageRoot.cloudCustomData) ? utils_1.JSONToObject(messageRoot === null || messageRoot === void 0 ? void 0 : messageRoot.cloudCustomData)
+                                : { messageReplies: {} };
+                            if ((_e = rootCloudCustomData === null || rootCloudCustomData === void 0 ? void 0 : rootCloudCustomData.messageReplies) === null || _e === void 0 ? void 0 : _e.replies) {
+                                rootCloudCustomData.messageReplies.replies = __spreadArrays((_f = rootCloudCustomData === null || rootCloudCustomData === void 0 ? void 0 : rootCloudCustomData.messageReplies) === null || _f === void 0 ? void 0 : _f.replies, [
+                                    repliesObject,
+                                ]);
+                            }
+                            else {
+                                rootCloudCustomData.messageReplies = {
+                                    replies: [repliesObject],
+                                    version: 1
+                                };
+                            }
+                            messageRoot.cloudCustomData = JSON.stringify(rootCloudCustomData);
+                            imResponse = this.modifyMessage(messageRoot);
+                            resolve(imResponse);
+                            return [3 /*break*/, 4];
+                        case 3:
+                            error_19 = _g.sent();
+                            reject(error_19);
+                            return [3 /*break*/, 4];
+                        case 4: return [2 /*return*/];
+                    }
+                });
+            }); });
+        };
+        var retryBreakFunction = function (error) {
+            if (error && (error === null || error === void 0 ? void 0 : error.code) === 2480)
+                return false;
+            return true;
+        };
+        return this.handlePromiseCallbackRetry(replyFunction, [500, 1000, 3000], retryBreakFunction);
+    };
+    /**
+     * 撤回回复消息
+     * revoke reply message
+     * @param {Array.<message>} message 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.revokeReplyMessage = function (message, messageRoot) {
+        var _this = this;
+        var revokeReplyFunction = function () {
+            return _this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                var cloudCustomData, messageRootID_2, rootCloudCustomData, index, imResponse, error_20;
+                var _a, _b, _c, _d, _e, _f;
+                return __generator(this, function (_g) {
+                    switch (_g.label) {
+                        case 0:
+                            _g.trys.push([0, 3, , 4]);
+                            if (!!messageRoot) return [3 /*break*/, 2];
+                            cloudCustomData = utils_1.JSONToObject(message === null || message === void 0 ? void 0 : message.cloudCustomData);
+                            messageRootID_2 = (_a = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReply) === null || _a === void 0 ? void 0 : _a.messageRootID;
+                            return [4 /*yield*/, ((_c = (_b = this === null || this === void 0 ? void 0 : this.currentStore) === null || _b === void 0 ? void 0 : _b.messageList) === null || _c === void 0 ? void 0 : _c.find(function (item) { return (item === null || item === void 0 ? void 0 : item.ID) === messageRootID_2; }))];
+                        case 1:
+                            messageRoot =
+                                (_g.sent()) || this.findMessage(messageRootID_2);
+                            _g.label = 2;
+                        case 2:
+                            rootCloudCustomData = (messageRoot === null || messageRoot === void 0 ? void 0 : messageRoot.cloudCustomData) ? utils_1.JSONToObject(messageRoot === null || messageRoot === void 0 ? void 0 : messageRoot.cloudCustomData)
+                                : { messageReplies: {} };
+                            if ((_d = rootCloudCustomData === null || rootCloudCustomData === void 0 ? void 0 : rootCloudCustomData.messageReplies) === null || _d === void 0 ? void 0 : _d.replies) {
+                                index = rootCloudCustomData.messageReplies.replies.findIndex(function (item) { return (item === null || item === void 0 ? void 0 : item.messageID) === (message === null || message === void 0 ? void 0 : message.ID); });
+                                (_f = (_e = rootCloudCustomData === null || rootCloudCustomData === void 0 ? void 0 : rootCloudCustomData.messageReplies) === null || _e === void 0 ? void 0 : _e.replies) === null || _f === void 0 ? void 0 : _f.splice(index, 1);
+                            }
+                            messageRoot.cloudCustomData = JSON.stringify(rootCloudCustomData);
+                            imResponse = this.modifyMessage(messageRoot);
+                            resolve(imResponse);
+                            return [3 /*break*/, 4];
+                        case 3:
+                            error_20 = _g.sent();
+                            reject(error_20);
+                            return [3 /*break*/, 4];
+                        case 4: return [2 /*return*/];
+                    }
+                });
+            }); });
+        };
+        var retryBreakFunction = function (error) {
+            if (error && (error === null || error === void 0 ? void 0 : error.code) === 2480)
+                return false;
+            return true;
+        };
+        return this.handlePromiseCallbackRetry(revokeReplyFunction, [500, 1000, 3000], retryBreakFunction);
+    };
+    /**
+     * 表情回应
+     * emoji react
+     * @param {Array.<message>} message 消息实例/message
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.emojiReact = function (message, emojiID) {
+        var _this = this;
+        var emojiReactFunction = function () {
+            return _this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                var userID, cloudCustomData, index, imResponse, error_21;
+                var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
+                return __generator(this, function (_u) {
+                    switch (_u.label) {
+                        case 0:
+                            _u.trys.push([0, 2, , 3]);
+                            if (!message || !(message === null || message === void 0 ? void 0 : message.ID) || !emojiID)
+                                reject();
+                            userID = (_e = (_d = (_c = (_b = (_a = this.TUICore) === null || _a === void 0 ? void 0 : _a.TUIServer) === null || _b === void 0 ? void 0 : _b.TUIProfile) === null || _c === void 0 ? void 0 : _c.store) === null || _d === void 0 ? void 0 : _d.profile) === null || _e === void 0 ? void 0 : _e.userID;
+                            return [4 /*yield*/, ((_g = (_f = this === null || this === void 0 ? void 0 : this.currentStore) === null || _f === void 0 ? void 0 : _f.messageList) === null || _g === void 0 ? void 0 : _g.find(function (item) { return (item === null || item === void 0 ? void 0 : item.ID) === (message === null || message === void 0 ? void 0 : message.ID); }))];
+                        case 1:
+                            message =
+                                (_u.sent()) || this.findMessage(message === null || message === void 0 ? void 0 : message.ID);
+                            cloudCustomData = (message === null || message === void 0 ? void 0 : message.cloudCustomData) ? utils_1.JSONToObject(message === null || message === void 0 ? void 0 : message.cloudCustomData)
+                                : { messageReact: {} };
+                            if ((_h = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _h === void 0 ? void 0 : _h.reacts) {
+                                if ((_j = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _j === void 0 ? void 0 : _j.reacts[emojiID]) {
+                                    index = (_l = (_k = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _k === void 0 ? void 0 : _k.reacts[emojiID]) === null || _l === void 0 ? void 0 : _l.indexOf(userID);
+                                    if (index === -1) {
+                                        (_o = (_m = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _m === void 0 ? void 0 : _m.reacts[emojiID]) === null || _o === void 0 ? void 0 : _o.push(userID);
+                                    }
+                                    else {
+                                        (_q = (_p = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _p === void 0 ? void 0 : _p.reacts[emojiID]) === null || _q === void 0 ? void 0 : _q.splice(index, 1);
+                                        if (((_s = (_r = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _r === void 0 ? void 0 : _r.reacts[emojiID]) === null || _s === void 0 ? void 0 : _s.length) === 0) {
+                                            (_t = cloudCustomData === null || cloudCustomData === void 0 ? void 0 : cloudCustomData.messageReact) === null || _t === void 0 ? true : delete _t.reacts[emojiID];
+                                        }
+                                    }
+                                }
+                                else {
+                                    cloudCustomData.messageReact.reacts[emojiID] = [userID];
+                                }
+                            }
+                            else {
+                                cloudCustomData.messageReact = {
+                                    reacts: {},
+                                    version: 1
+                                };
+                                cloudCustomData.messageReact.reacts[emojiID] = [userID];
+                            }
+                            message.cloudCustomData = JSON.stringify(cloudCustomData);
+                            imResponse = this.modifyMessage(message);
+                            resolve(imResponse);
+                            return [3 /*break*/, 3];
+                        case 2:
+                            error_21 = _u.sent();
+                            reject(error_21);
+                            return [3 /*break*/, 3];
+                        case 3: return [2 /*return*/];
+                    }
+                });
+            }); });
+        };
+        var retryBreakFunction = function (error) {
+            if (error && (error === null || error === void 0 ? void 0 : error.code) === 2480)
+                return false;
+            return true;
+        };
+        return this.handlePromiseCallbackRetry(emojiReactFunction, [500, 1000, 3000], retryBreakFunction);
+    };
+    /**
+     * 查询消息
+     * find message
+     * @param {String} messageID 消息实例ID/messageID
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.findMessage = function (messageID) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, error_22;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.findMessage(messageID)];
+                    case 1:
+                        imResponse = _a.sent();
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_22 = _a.sent();
+                        reject(error_22);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 获取群组属性
+     * get group profile
+     *
+     * @param {any} options 参数
+     * @param {String} options.groupID 群组ID
+     * @param {Array.<String>} options.groupProfileFilter 群资料过滤器
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getGroupProfile = function (options) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, error_23;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.getGroupProfile(options)];
+                    case 1:
+                        imResponse = _a.sent();
+                        this.currentStore.conversation.groupProfile = imResponse.data.group;
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_23 = _a.sent();
+                        reject(error_23);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 获取群成员资料
+     * get group member profile
+     *
+     * @param {any} options 参数
+     * @param {String} options.groupID 群组ID
+     * @param {Array.<String>} options.userIDList 要查询的群成员用户 ID 列表
+     * @param {	Array.<String>} options.memberCustomFieldFilter 群成员自定义字段筛选
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getGroupMemberProfile = function (options) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, error_24;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.getGroupMemberProfile(options)];
+                    case 1:
+                        imResponse = _a.sent();
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_24 = _a.sent();
+                        reject(error_24);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 处理申请加群
+     * handling group application
+     * - 管理员
+     *   administrator
+     *
+     * @param {any} options 参数
+     * @param {String} options.handleAction 处理结果 Agree(同意) / Reject(拒绝)
+     * @param {String} options.handleMessage 附言
+     * @param {Message} options.message 对应【群系统通知】的消息实例
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.handleGroupApplication = function (options) {
+        var _this = this;
+        return this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+            var imResponse, error_25;
+            return __generator(this, function (_a) {
+                switch (_a.label) {
+                    case 0:
+                        _a.trys.push([0, 2, , 3]);
+                        return [4 /*yield*/, this.TUICore.tim.handleGroupApplication(options)];
+                    case 1:
+                        imResponse = _a.sent();
+                        resolve(imResponse);
+                        return [3 /*break*/, 3];
+                    case 2:
+                        error_25 = _a.sent();
+                        reject(error_25);
+                        return [3 /*break*/, 3];
+                    case 3: return [2 /*return*/];
+                }
+            });
+        }); });
+    };
+    /**
+     * 获取其他用户资料
+     * get user profile
+     *
+     * @param {Array<string>} userIDList 用户的账号列表/userID list
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getUserProfile = function (userIDList) {
+        return __awaiter(this, void 0, void 0, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_26;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.getUserProfile({
+                                            userIDList: userIDList
+                                        })];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_26 = _a.sent();
+                                    reject(error_26);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 获取 SDK 缓存的好友列表
+     * Get the friend list cached by the SDK
+     *
+     * @param {Array<string>} userIDList 用户的账号列表
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getFriendList = function () {
+        return __awaiter(this, void 0, Promise, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_27;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.getFriendList()];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_27 = _a.sent();
+                                    reject(error_27);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 校验好友关系
+     * check friend
+     *
+     * @param {string} userID 用户账号
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.checkFriend = function (userID, type) {
+        return __awaiter(this, void 0, Promise, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, isFriendShip, error_28;
+                        var _a, _b;
+                        return __generator(this, function (_c) {
+                            switch (_c.label) {
+                                case 0:
+                                    _c.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.checkFriend({
+                                            userIDList: [userID],
+                                            type: type
+                                        })];
+                                case 1:
+                                    imResponse = _c.sent();
+                                    isFriendShip = (_b = (_a = imResponse === null || imResponse === void 0 ? void 0 : imResponse.data) === null || _a === void 0 ? void 0 : _a.successUserIDList[0]) === null || _b === void 0 ? void 0 : _b.relation;
+                                    resolve(isFriendShip);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_28 = _c.sent();
+                                    reject(error_28);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 获取群消息已读成员列表
+     * Get the list of memebers who have read the group message.
+     *
+     * @param {message} message 消息实例/message
+     * @param {string} cursor 分页拉取的游标,第一次拉取传''/Paging pull the cursor,first pull pass ''
+     * @param {number} count 分页拉取的个数/The number of page pulls
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getGroupReadMemberList = function (message, cursor, count) {
+        if (cursor === void 0) { cursor = ""; }
+        if (count === void 0) { count = 15; }
+        return __awaiter(this, void 0, Promise, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_29;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.getGroupMessageReadMemberList({
+                                            message: message,
+                                            filter: 0,
+                                            cursor: cursor,
+                                            count: count
+                                        })];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_29 = _a.sent();
+                                    reject(error_29);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 获取群消息未读成员列表
+     * Get the list of memebers who have not read the group message.
+     *
+     * @param {message} message 消息实例/message
+     * @param {string} cursor 分页拉取的游标,第一次拉取传''/Paging pull the cursor,first pull pass ''
+     * @param {number} count 分页拉取的个数/The number of page pulls
+     * @returns {Promise}
+     */
+    TUIChatServer.prototype.getGroupUnreadMemberList = function (message, cursor, count) {
+        if (cursor === void 0) { cursor = ""; }
+        if (count === void 0) { count = 15; }
+        return __awaiter(this, void 0, Promise, function () {
+            var _this = this;
+            return __generator(this, function (_a) {
+                return [2 /*return*/, this.handlePromiseCallback(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
+                        var imResponse, error_30;
+                        return __generator(this, function (_a) {
+                            switch (_a.label) {
+                                case 0:
+                                    _a.trys.push([0, 2, , 3]);
+                                    return [4 /*yield*/, this.TUICore.tim.getGroupMessageReadMemberList({
+                                            message: message,
+                                            filter: 1,
+                                            cursor: cursor,
+                                            count: count
+                                        })];
+                                case 1:
+                                    imResponse = _a.sent();
+                                    resolve(imResponse);
+                                    return [3 /*break*/, 3];
+                                case 2:
+                                    error_30 = _a.sent();
+                                    reject(error_30);
+                                    return [3 /*break*/, 3];
+                                case 3: return [2 /*return*/];
+                            }
+                        });
+                    }); })];
+            });
+        });
+    };
+    /**
+     * 自己发送消息上屏显示
+     *
+     * @param {message} message 消息实例/message
+     */
+    TUIChatServer.prototype.handleMessageSentByMeToView = function (message) {
+        var _a, _b;
+        return __awaiter(this, void 0, void 0, function () {
+            return __generator(this, function (_c) {
+                if ((message === null || message === void 0 ? void 0 : message.conversationID) === ((_b = (_a = this === null || this === void 0 ? void 0 : this.store) === null || _a === void 0 ? void 0 : _a.conversation) === null || _b === void 0 ? void 0 : _b.conversationID)) {
+                    this.currentStore.messageList.push(message);
+                }
+                return [2 /*return*/];
+            });
+        });
+    };
+    /**
+     * /////////////////////////////////////////////////////////////////////////////////
+     * //
+     * //                                    UI 数据绑定server数据同步
+     * //                           UI data binding server data synchronization
+     * //
+     * /////////////////////////////////////////////////////////////////////////////////
+     */
+    /**
+     * 赋值
+     * bind
+     *
+     * @param {Object} params 使用的数据/params
+     * @returns {Object} 数据/data
+     */
+    TUIChatServer.prototype.bind = function (params) {
+        return (this.currentStore = params);
+    };
+    return TUIChatServer;
+}(IComponentServer_1["default"]));
+exports["default"] = TUIChatServer;

+ 105 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/index.ts

@@ -0,0 +1,105 @@
+import TUIChatServer from './server';
+import TUIChatComponent from './index.vue';
+
+import Face from './plugin-components/face';
+import Image from './plugin-components/image';
+import Video from './plugin-components/video';
+import File from './plugin-components/file';
+import Forward from './plugin-components/forward';
+import Words from './plugin-components/words';
+import Evaluate from './plugin-components/evaluate';
+import TypingHeader from './plugin-components/typingHeader';
+import ReadReceiptDialog from './plugin-components/readReceiptDialog';
+import Replies from './plugin-components/replies';
+import Call from './plugin-components/call';
+import ImagePreviewer from './plugin-components/imagePreviewer';
+import MessageInput from './message-input';
+
+let sendComponents: any = {
+  Face,
+  Image,
+  Video,
+  File
+  // Evaluate,
+  // Words,
+  // Call,
+};
+
+export const messageComponents: any = {
+  Forward
+};
+
+export const otherComponents: any = {
+  TypingHeader,
+  ReadReceiptDialog,
+  Replies,
+  ImagePreviewer,
+  MessageInput
+};
+
+export function getComponents(type: string) {
+  let options: any = {};
+  switch (type) {
+    case 'send':
+      options = sendComponents;
+      break;
+    case 'message':
+      options = messageComponents;
+      break;
+    case 'other':
+      options = otherComponents;
+      break;
+    default:
+      break;
+  }
+  return options;
+}
+
+const install = (app: any) => {
+  const components: any = {
+    ...sendComponents,
+    ...messageComponents,
+    ...otherComponents
+  };
+  Object.keys(components).forEach((name: any) => {
+    components[name].TUIServer = TUIChat.server;
+  });
+  TUIChatComponent.TUIServer = TUIChat.server;
+  TUIChatComponent.components = {
+    ...TUIChatComponent.components,
+    ...components
+  };
+  app.component(TUIChat.name, TUIChatComponent);
+};
+
+const plugin = (TUICore: any) => {
+  (TUIChat.server as any) = new TUIChatServer(TUICore);
+  TUICore.component(TUIChat.name, TUIChat);
+  return TUIChat;
+};
+
+const setPluginComponents = (Components: any) => {
+  sendComponents = { ...sendComponents, ...Components };
+};
+
+const removePluginComponents = (nameList: Array<string>) => {
+  nameList.map((name: string) => {
+    delete sendComponents[name];
+    return name;
+  });
+};
+
+const TUIChat = {
+  name: 'TUIChat',
+  component: TUIChatComponent,
+  server: TUIChatServer,
+  sendComponents,
+  messageComponents,
+  otherComponents,
+  install,
+  plugin,
+  setPluginComponents,
+  removePluginComponents
+};
+
+export default TUIChat;

+ 976 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/index.vue

@@ -0,0 +1,976 @@
+<template>
+  <div
+    class="TUIChat"
+    :class="[env.isH5 ? 'TUIChat-H5' : '']"
+    v-if="conversationType === 'chat'"
+  >
+    <header class="TUIChat-header">
+      <i class="icon icon-back" @click="back" v-if="env.isH5"></i>
+      <TypingHeader
+        :needTyping="needTyping"
+        :conversation="conversation"
+        :messageList="messageList"
+        ref="typingRef"
+      />
+      <aside class="setting">
+        <Manage
+          v-if="conversation.groupProfile"
+          :conversation="conversation"
+          :userInfo="userInfo"
+          :isH5="env.isH5"
+        />
+        <Replies
+          :message="currentMessage"
+          :conversation="conversation"
+          :show="repliesDialogStatus"
+          :isH5="env.isH5"
+          :messageList="messageList"
+          @closeDialog="closeDialog"
+          ref="repliesDialog"
+        />
+      </aside>
+    </header>
+    <div class="TUIChat-main">
+      <ul
+        class="TUI-message-list"
+        @click="dialogID = ''"
+        ref="messageEle"
+        id="messageEle"
+      >
+        <p
+          class="message-more"
+          @click="getHistoryMessageList"
+          v-if="!isCompleted"
+        >
+          {{ $t('TUIChat.查看更多') }}
+        </p>
+        <li
+          v-for="(item, index) in messages"
+          :key="index"
+          :id="item?.ID"
+          ref="messageAimID"
+        >
+          <MessageTimestamp
+            :currTime="item?.time"
+            :prevTime="index > 0 ? messages[index - 1]?.time : 0"
+          ></MessageTimestamp>
+          <MessageItem
+            :message="item"
+            :env="env"
+            :types="types"
+            :displayGroupMessageReadReceipt="needGroupReceipt"
+            :displayEmojiReactions="isNeedEmojiReact"
+            :messageList="messages"
+            @handleEditor="handleEditor"
+            @showDialog="showDialog"
+            @uploading="handleUploadingImageOrVideo"
+            @jumpID="jumpID"
+            @resendMessage="resendMessage"
+          ></MessageItem>
+        </li>
+        <div
+          class="to-bottom-tip"
+          v-if="needToBottom"
+          @click="scrollToTarget('bottom')"
+        >
+          <i class="icon icon-bottom-double"></i>
+          <div class="to-bottom-tip-cont">
+            <span>{{ toBottomTipCont }}</span>
+          </div>
+        </div>
+      </ul>
+      <div
+        class="dialog dialog-conversation"
+        v-if="forwardStatus && messageComponents.Forward"
+      >
+        <component
+          :is="'Forward'"
+          :list="conversationData.list"
+          :message="currentMessage"
+          :show="forwardStatus"
+          :isH5="env.isH5"
+          @update:show="(e: any) => (forwardStatus = e)"
+        >
+          <template #left="{ data }">
+            <img class="avatar" :src="conversationData.handleAvatar(data)" />
+            <label class="name">{{ conversationData.handleName(data) }}</label>
+          </template>
+          <template #right="{ data }">
+            <img class="avatar" :src="conversationData.handleAvatar(data)" />
+            <label class="name" v-if="!env.isH5">
+              {{ conversationData.handleName(data) }}
+            </label>
+          </template>
+        </component>
+      </div>
+      <div class="dialog dialog-conversation">
+        <ReadReceiptDialog
+          :message="currentMessage"
+          :conversation="conversation"
+          :show="receiptDialogStatus"
+          :isH5="env.isH5"
+          @closeDialog="closeDialog"
+          ref="readReceiptDialog"
+        />
+      </div>
+      <imagePreviewer
+        v-if="showImagePreview"
+        :currentImage="currentImagePreview"
+        :imageList="imageList"
+        @close="showImagePreview = false"
+      />
+    </div>
+    <div
+      class="TUIChat-footer"
+      :class="[isMute && 'disabled', env.isH5 && 'TUIChat-H5-footer']"
+    >
+      <div class="func" id="func">
+        <main class="func-main">
+          <component
+            v-for="(item, index) in pluginComponentList"
+            :key="index"
+            :isMute="isMute"
+            :is="item"
+            :isH5="env.isH5"
+            :conversation="conversation"
+            parentID="func"
+            @send="handleSend"
+          ></component>
+        </main>
+      </div>
+      <MessageInput
+        ref="messageInput"
+        :conversation="conversation"
+        :memberList="allMemberList"
+        :env="env"
+        :isGroup="userInfo?.isGroup"
+        :replyOrReference="reference"
+        :isMute="isMute"
+        :muteText="muteText"
+        :placeholder="$t('TUIChat.请输入消息')"
+        @sendMessage="reportMessageSend"
+        @resetReplyOrReference="resetReplyOrReference"
+        @onTyping="handleTyping"
+      ></MessageInput>
+    </div>
+    <div v-show="showResend" class="mask" @click="showResend = false">
+      <div class="mask-main">
+        <header>{{ $t('TUIChat.确认重发该消息?') }}</header>
+        <footer>
+          <p @click="showResend = false">{{ $t('TUIChat.取消') }}</p>
+          <i></i>
+          <p @click="submit">{{ $t('TUIChat.确定') }}</p>
+        </footer>
+      </div>
+    </div>
+  </div>
+  <div class="TUIChat" v-else-if="conversationType === 'system'">
+    <header class="TUIChat-header">
+      <h1>{{ conversationName }}</h1>
+    </header>
+    <MessageSystem
+      :data="messages"
+      :types="types"
+      @application="handleApplication"
+    />
+  </div>
+  <slot v-else-if="slotDefault" />
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  reactive,
+  toRefs,
+  ref,
+  computed,
+  nextTick,
+  watch,
+  useSlots,
+  onMounted,
+  watchEffect
+} from 'vue';
+import { MessageSystem, MessageItem, MessageTimestamp } from './components';
+import { onClickOutside } from '@vueuse/core';
+import { Manage } from './manage-components';
+
+import {
+  handleAvatar,
+  handleName,
+  getImgLoad,
+  isTypingMessage,
+  deepCopy,
+  isMessageTip,
+  handleReferenceForShow
+} from './utils/utils';
+
+import { getComponents } from './index';
+
+import { useStore } from 'vuex';
+import constant from '../constant';
+import { handleErrorPrompts } from '../utils';
+import Link from '../../../utils/link';
+import { Message } from './interface';
+import { Conversation } from '../TUIConversation/interface';
+
+import MessageInput from './message-input';
+
+const TUIChat: any = defineComponent({
+  name: 'TUIChat',
+  components: {
+    MessageSystem,
+    MessageTimestamp,
+    Manage,
+    MessageInput,
+    MessageItem
+  },
+  props: {
+    isMsgNeedReadReceipt: {
+      type: Boolean,
+      default: false
+    },
+    isNeedTyping: {
+      type: Boolean,
+      default: true
+    },
+    isNeedEmojiReact: {
+      type: Boolean,
+      default: true
+    }
+  },
+  setup(props) {
+    const { TUIServer } = TUIChat;
+    const GroupServer = TUIServer?.TUICore?.TUIServer?.TUIGroup;
+    const ProfileServer = TUIServer?.TUICore?.TUIServer?.TUIProfile;
+    const VuexStore =
+      (TUIServer.TUICore.isOfficial && useStore && useStore()) || {};
+    const { t } = (window as any).TUIKitTUICore.config.i18n.useI18n();
+    const data = reactive({
+      messageList: [] as Message[],
+      conversation: {} as Conversation,
+      text: '',
+      atText: '',
+      types: TUIServer.TUICore.TIM.TYPES,
+      currentMessage: {} as Message,
+      dialogID: '',
+      forwardStatus: false,
+      receiptDialogStatus: false,
+      repliesDialogStatus: false,
+      showImagePreview: false,
+      currentImagePreview: {} as Message,
+      isCompleted: false,
+      userInfoView: false,
+      userInfo: {
+        isGroup: false,
+        list: []
+      },
+      selfInfo: {},
+      messageComponents: getComponents('message'),
+      isShow: false,
+      muteText: '您已被管理员禁言',
+      isFirstSend: true,
+      isFirstRender: true,
+      showGroupMemberList: false,
+      reference: {
+        message: {} as Message,
+        content: '',
+        type: 0, // message type
+        show: '' // 'reference' or 'reply'
+      },
+      historyReference: false,
+      referenceID: '',
+      allMemberList: [],
+      env: TUIServer.TUICore.TUIEnv,
+      showResend: false,
+      resendMessage: {},
+      inputBlur: false,
+      inputComposition: false,
+      inputCompositionCont: '',
+      needTyping: props.isNeedTyping,
+      needReadReceipt: false,
+      peerNeedReceipt: false,
+      needToBottom: false,
+      toBottomTipCont: '',
+      messageInView: [] as Message[],
+      readSet: new Set(),
+      isUserAction: false,
+      scroll: {
+        scrollTop: 0,
+        scrollHeight: 0,
+        scrollTopMin: Infinity,
+        scrollTopMax: 0
+      },
+      isMsgNeedReadReceipt: false,
+      isNeedEmojiReact: false,
+      dropDownRef: null,
+      typingRef: null
+    });
+
+    const slotDefault = !!useSlots().default;
+    // 调用 TUIConversation 模块的 setMessageRead 方法
+    // 消息已读
+    // Using the setMessageRead method of the TUIConversion module
+    const setMessageRead = async (conversationID: string | undefined) => {
+      if (!conversationID) return;
+      await TUIServer?.TUICore?.TUIServer?.TUIConversation?.setMessageRead(
+        conversationID
+      );
+      return;
+    };
+
+    const sendMessageReadReceipt = async (messageList: Message[]) => {
+      const needReceiptMessageList = messageList.filter(
+        (item: Message) =>
+          item?.flow === 'in' &&
+          item?.needReadReceipt &&
+          !data.readSet.has(item?.ID)
+      );
+      if (needReceiptMessageList.length) {
+        await TUIServer?.sendMessageReadReceipt(needReceiptMessageList).then(
+          () => {
+            needReceiptMessageList.forEach((item: Message) =>
+              data.readSet.add(item?.ID)
+            );
+          }
+        );
+      }
+      await setMessageRead(data?.conversation?.conversationID);
+    };
+
+    const pluginComponentList: any = [];
+    Object.keys(getComponents('send')).forEach((name: any) => {
+      pluginComponentList.push(name);
+    });
+
+    Manage.TUIServer = TUIServer;
+    Manage.GroupServer = TUIServer?.TUICore?.TUIServer?.TUIGroup;
+
+    const messageEle = ref();
+    const inputEle: any = ref();
+    const messageAimID = ref();
+    const readReceiptDialog = ref();
+    const messageInput = ref();
+
+    TUIServer.bind(data);
+
+    const conversationData = {
+      list: [],
+      handleAvatar,
+      handleName
+    };
+
+    const dialog: any = ref();
+
+    onClickOutside(dialog, () => {
+      data.showGroupMemberList = false;
+    });
+
+    const conversationType = computed(() => {
+      const { conversation } = data;
+      if (!conversation?.conversationID) {
+        return '';
+      }
+      if (conversation?.type === TUIServer.TUICore.TIM.TYPES.CONV_SYSTEM) {
+        return 'system';
+      }
+      return 'chat';
+    });
+
+    const isMute = computed(() => {
+      const { conversation } = data;
+      if (conversation?.type === TUIServer.TUICore.TIM.TYPES.CONV_GROUP) {
+        const userRole = conversation?.groupProfile?.selfInfo.role;
+        const isMember =
+          userRole === TUIServer.TUICore.TIM.TYPES.GRP_MBR_ROLE_MEMBER;
+        if (isMember && conversation?.groupProfile?.muteAllMembers) {
+          // data.muteText = "管理员开启全员禁言";
+          return true;
+        }
+        const time: number = new Date().getTime();
+        if ((data.selfInfo as any)?.muteUntil * 1000 - time > 0) {
+          // data.muteText = "您已被管理员禁言";
+          return true;
+        }
+      }
+      return false;
+    });
+
+    watchEffect(() => {
+      data.isMsgNeedReadReceipt = props.isMsgNeedReadReceipt;
+      data.needReadReceipt = data.isMsgNeedReadReceipt;
+      data.needTyping = props.isNeedTyping;
+      data.isNeedEmojiReact = props.isNeedEmojiReact;
+    });
+
+    watch(
+      () => data?.conversation?.conversationID,
+      (newVal: () => string | undefined, oldVal: () => string | undefined) => {
+        if (newVal === oldVal) return;
+        data.scroll.scrollTop = 0;
+        data.scroll.scrollHeight = 0;
+        data.scroll.scrollTopMin = Infinity;
+        data.scroll.scrollTopMax = 0;
+        data.text = '';
+        data.atText = '';
+        data.reference = {
+          message: {} as Message,
+          content: '',
+          type: 0,
+          show: ''
+        };
+      },
+      {
+        deep: true
+      }
+    );
+
+    watch(isMute, (newVal: any, oldVal: any) => {
+      const { conversation } = data;
+      if (
+        newVal &&
+        conversation?.type === TUIServer.TUICore.TIM.TYPES.CONV_GROUP
+      ) {
+        const userRole = conversation?.groupProfile?.selfInfo.role;
+        const isMember =
+          userRole === TUIServer.TUICore.TIM.TYPES.GRP_MBR_ROLE_MEMBER;
+        if (isMember && conversation?.groupProfile?.muteAllMembers) {
+          data.muteText = '管理员开启全员禁言';
+        }
+        const time: number = new Date().getTime();
+        if ((data.selfInfo as any)?.muteUntil * 1000 - time > 0) {
+          data.muteText = '您已被管理员禁言';
+        }
+      }
+    });
+
+    const conversationName = computed(() => {
+      const { conversation } = data;
+      return handleName(conversation);
+    });
+
+    const messages = computed(() =>
+      data.messageList.filter(
+        (item: any) => !item.isDeleted && !isTypingMessage(item)
+      )
+    );
+    const imageList = computed(() =>
+      messages?.value?.filter((item: Message) => {
+        return !item.isRevoked && item.type === data.types.MSG_IMAGE;
+      })
+    );
+
+    const needGroupReceipt = computed(() => {
+      const { conversation, needReadReceipt } = data;
+      if (
+        conversation?.type === TUIServer.TUICore.TIM.TYPES.CONV_C2C ||
+        needReadReceipt
+      ) {
+        return true;
+      }
+      return false;
+    });
+
+    watch(
+      messages,
+      (newVal: Array<Message>, oldVal: Array<Message>) => {
+        nextTick(() => {
+          const isTheSameMessage =
+            newVal[newVal.length - 1]?.ID === oldVal[oldVal.length - 1]?.ID;
+          if (newVal.length === 0 || isTheSameMessage) {
+            return;
+          }
+          handleScroll();
+        });
+        if (data.currentMessage) {
+          const messageID = data.currentMessage?.ID;
+          const message = newVal.find((item: any) => item.ID === messageID);
+          if (message) {
+            data.currentMessage = deepCopy(message);
+          }
+        }
+        if (data.historyReference) {
+          for (let index = 0; index < messages.value.length; index++) {
+            if (messages?.value[index]?.ID === data?.referenceID) {
+              scrollToTarget('target', messageAimID.value[index]);
+              messageAimID.value[index]
+                .getElementsByClassName('content')[0]
+                .classList.add('reference-content');
+            }
+          }
+          data.historyReference = false;
+        }
+      },
+      { deep: true }
+    );
+
+    watch(
+      () => data.scroll.scrollTop,
+      (newVal: number) => {
+        setTimeout(() => {
+          // scrolling end
+          if (newVal === messageEle?.value?.scrollTop) {
+            if (
+              data.scroll.scrollTopMin !== Infinity &&
+              data.scroll.scrollTopMax !== 0
+            ) {
+              sendMessageReadInView('scroll');
+            }
+            data.scroll.scrollTopMin = Infinity;
+            data.scroll.scrollTopMax = 0;
+          }
+        }, 20);
+      },
+      { deep: true }
+    );
+
+    onMounted(() => {
+      watch(
+        () => messageEle?.value,
+        () => {
+          if (messageEle?.value) {
+            messageEle.value.addEventListener('scroll', onScrolling);
+          }
+        },
+        {
+          deep: true
+        }
+      );
+    });
+
+    const handleSend = (emoji: any) => {
+      messageInput?.value?.addEmoji(emoji);
+    };
+
+    const reportMessageSend = async (event: any) => {
+      if (data.isFirstSend) {
+        data.isFirstSend = false;
+      }
+      data.reference.show = '';
+      TUIServer.TUICore.isOfficial &&
+        VuexStore?.commit &&
+        VuexStore?.commit('handleTask', 0);
+    };
+
+    const handleItem = (item: any) => {
+      data.currentMessage = item;
+      data.dialogID = item.ID;
+    };
+
+    const resendMessage = (message: Message) => {
+      if (data.env.isH5) {
+        data.showResend = true;
+        data.resendMessage = message;
+      } else {
+        TUIServer.resendMessage(message).catch((error: any) => {
+          handleErrorPrompts(error, data.env);
+        });
+      }
+    };
+
+    const forwardMessage = (message: Message) => {
+      data.currentMessage = message;
+      conversationData.list =
+        TUIServer.TUICore.getStore().TUIConversation.conversationList;
+      data.forwardStatus = true;
+    };
+
+    const referOrReplyMessage = (message: Message, type: any) => {
+      let replyObj = handleReferenceForShow(message);
+      data.reference = {
+        message,
+        content: replyObj?.referenceMessageForShow,
+        type: replyObj?.referenceMessageType,
+        show: type
+      };
+    };
+
+    const submit = () => {
+      TUIServer.resendMessage(data.resendMessage)
+        .then(() => {
+          data.showResend = false;
+        })
+        .catch((error: any) => {
+          handleErrorPrompts(error, data.env);
+          data.showResend = false;
+        });
+    };
+
+    const handleEdit = (item: any) => {
+      if (!item?.payload?.text) return;
+      messageInput?.value?.reEdit(item?.payload?.text);
+    };
+
+    const handleApplication = (options: any) => {
+      TUIServer.handleGroupApplication(options);
+    };
+
+    const pasting = (e: any) => {
+      if (e.clipboardData.files[0]) {
+        TUIServer.sendImageMessage(e.clipboardData.files[0]);
+      }
+    };
+
+    const getHistoryMessageList = async () => {
+      await TUIServer.getHistoryMessageList().then(() => {
+        scrollToTarget('target', messageEle?.value?.firstElementChild);
+      });
+    };
+
+    const jumpID = (messageID: string) => {
+      data.referenceID = messageID;
+      const list: any = [];
+      // If the referenced message is in the current messageList, you can jump directly. Otherwise, you need to pull the historical message
+      for (let index = 0; index < messages.value.length; index++) {
+        list.push(messages?.value[index]?.ID);
+        if (
+          list.indexOf(messageID) !== -1 &&
+          messages.value[index]?.ID === messageID
+        ) {
+          scrollToTarget('target', messageAimID.value[index]);
+          messageAimID.value[index]
+            .getElementsByClassName('content')[0]
+            .classList.remove('reference-content');
+          nextTick(() => {
+            messageAimID.value[index]
+              .getElementsByClassName('content')[0]
+              .classList.add('reference-content');
+          });
+        }
+      }
+      if (list.indexOf(messageID) === -1) {
+        TUIServer.getHistoryMessageList().then(() => {
+          data.historyReference = true;
+        });
+      }
+    };
+
+    const toggleshowGroupMemberList = () => {
+      data.showGroupMemberList = !data.showGroupMemberList;
+    };
+
+    const back = () => {
+      TUIServer.TUICore.TUIServer.TUIConversation.handleCurrentConversation();
+    };
+
+    const openLink = (type: any) => {
+      window.open(type.url);
+    };
+
+    const handleEditor = (message: Message, type: string) => {
+      if (!message || !type) return;
+      switch (type) {
+        case 'reference':
+          referOrReplyMessage(message, type);
+          break;
+        case 'reply':
+          referOrReplyMessage(message, type);
+          break;
+        case 'reedit':
+          if (message?.payload?.text) {
+            messageInput?.value?.reEdit(message?.payload?.text);
+          }
+          break;
+        default:
+          break;
+      }
+    };
+
+    const showDialog = async (message: Message, type: string) => {
+      if (!message?.ID || !type) return;
+      switch (type) {
+        case 'receipt':
+          if (
+            message.conversationType !==
+              TUIServer.TUICore.TIM.TYPES.CONV_GROUP ||
+            message.readReceiptInfo?.unreadCount === 0
+          ) {
+            return;
+          }
+          data.currentMessage = message;
+          data.receiptDialogStatus = true;
+          break;
+        case 'replies':
+          data.currentMessage = message;
+          data.repliesDialogStatus = true;
+          break;
+        case 'forward':
+          data.currentMessage = message;
+          conversationData.list =
+            TUIServer.TUICore.getStore().TUIConversation.conversationList;
+          data.forwardStatus = true;
+          break;
+        case 'previewImage':
+          data.showImagePreview = !data.showImagePreview;
+          data.currentImagePreview = message;
+          break;
+        default:
+          break;
+      }
+    };
+
+    const closeDialog = async (type: string) => {
+      if (!type) return;
+      switch (type) {
+        case 'receipt':
+          data.currentMessage = {};
+          data.receiptDialogStatus = false;
+          break;
+        case 'replies':
+          data.currentMessage = {};
+          data.repliesDialogStatus = false;
+          break;
+        default:
+          break;
+      }
+    };
+
+    const handleScroll = () => {
+      if (data.isFirstRender) {
+        data.needToBottom = false;
+        scrollToTarget('bottom');
+        data.isFirstRender = false;
+        return;
+      }
+      if (messageEle.value) {
+        const { scrollHeight, scrollTop, clientHeight } = messageEle.value;
+        if (
+          scrollHeight - (scrollTop + clientHeight) <= clientHeight ||
+          messages.value[messages.value.length - 1]?.flow === 'out'
+        ) {
+          scrollToTarget('bottom');
+        } else {
+          handleToBottomTip(true);
+        }
+      }
+    };
+
+    const scrollToTarget = (type: string, targetElement?: HTMLElement) => {
+      messageEle?.value?.removeEventListener('scroll', onScrolling);
+      data.isUserAction = true;
+      switch (type) {
+        case constant.scrollType.toBottom:
+          data.needToBottom = false;
+          nextTick(() => {
+            if (data?.env?.isH5) {
+              handleH5Scroll();
+            } else {
+              messageEle?.value?.lastElementChild?.scrollIntoView(false);
+            }
+            getImgLoad(messageEle?.value, 'message-img', async () => {
+              if (data?.env?.isH5) {
+                handleH5Scroll();
+              } else {
+                messageEle?.value?.lastElementChild?.scrollIntoView(false);
+              }
+              messageEle.value.addEventListener('scroll', onScrolling);
+              await sendMessageReadInView('page');
+            });
+          });
+          break;
+        case constant.scrollType.toTarget:
+          nextTick(() => {
+            targetElement?.scrollIntoView(false);
+            getImgLoad(messageEle?.value, 'message-img', async () => {
+              targetElement?.scrollIntoView(false);
+              messageEle.value.addEventListener('scroll', onScrolling);
+              await sendMessageReadInView('page');
+            });
+          });
+
+          break;
+        default:
+          break;
+      }
+    };
+
+    const handleH5Scroll = () => {
+      if (document?.getElementById('app')?.style) {
+        (document.getElementById('app') as any).style.marginBottom = ``;
+        (document.getElementById('app') as any).style.height = `100%`;
+      }
+      messageEle.value.scrollTop = messageEle.value.scrollHeight;
+    };
+
+    const onScrolling = () => {
+      const { scrollHeight, scrollTop, clientHeight } = messageEle.value;
+      if (needGroupReceipt.value) {
+        data.scroll.scrollHeight = scrollHeight;
+        data.scroll.scrollTop = scrollTop;
+        data.scroll.scrollTopMin = data.isUserAction
+          ? data.scroll.scrollTopMin
+          : Math.min(data.scroll.scrollTopMin, data.scroll.scrollTop);
+        data.scroll.scrollTopMax = data.isUserAction
+          ? data.scroll.scrollTopMax
+          : Math.max(data.scroll.scrollTopMax, data.scroll.scrollTop);
+      }
+      if (scrollHeight - (scrollTop + clientHeight) > clientHeight) {
+        handleToBottomTip(true);
+      } else {
+        handleToBottomTip(false);
+      }
+      data.isUserAction = false;
+    };
+
+    const handleToBottomTip = (needToBottom: boolean) => {
+      switch (needToBottom) {
+        case true:
+          data.needToBottom = true;
+          if (
+            data?.conversation?.unreadCount &&
+            data?.conversation?.unreadCount > 0
+          ) {
+            data.toBottomTipCont = `${data?.conversation?.unreadCount} ${t(
+              'TUIChat.条新消息'
+            )}`;
+          } else {
+            data.toBottomTipCont = t('TUIChat.回到最新位置');
+          }
+          break;
+        case false:
+          data.needToBottom = false;
+          break;
+        default:
+          data.needToBottom = false;
+          break;
+      }
+    };
+
+    const sendMessageReadInView = async (type: string) => {
+      if (!needGroupReceipt.value) {
+        setMessageRead(data?.conversation?.conversationID);
+        return;
+      }
+      if (data.messageInView.length) data.messageInView = [] as Message[];
+      let start = 0;
+      let end = 0;
+      switch (type) {
+        case constant.inViewType.page:
+          start = data.scroll.scrollTop;
+          end = data.scroll.scrollTop + messageEle?.value?.clientHeight;
+          break;
+        case constant.inViewType.scroll:
+          start = data.scroll.scrollTopMin;
+          end = data.scroll.scrollTopMax + messageEle?.value?.clientHeight;
+          break;
+        default:
+          break;
+      }
+      for (let i = 0; i < messageAimID?.value?.length; i++) {
+        if (isInView(type, messageAimID?.value[i], start, end)) {
+          const message = messages.value[i];
+          data.messageInView.push(message);
+        }
+      }
+      await sendMessageReadReceipt(data.messageInView);
+    };
+
+    const isInView = (
+      type: string,
+      dom: HTMLElement,
+      viewStart: number,
+      viewEnd: number
+    ) => {
+      const containerTop = messageEle.value.getBoundingClientRect().top;
+      const containerBottom = messageEle.value.getBoundingClientRect().bottom;
+      const { top, bottom } = dom.getBoundingClientRect();
+      const { offsetTop, clientHeight } = dom;
+      switch (type) {
+        case constant.inViewType.page:
+          return (
+            Math.round(top) >= Math.round(containerTop) &&
+            Math.round(bottom) <= Math.round(containerBottom)
+          );
+        case constant.inViewType.scroll:
+          return (
+            Math.round(offsetTop) >= Math.round(viewStart) &&
+            Math.round(offsetTop + clientHeight) <= Math.round(viewEnd)
+          );
+        default:
+          return false;
+      }
+    };
+
+    const handleDropDownOpen = (value: any) => {
+      if (data.dropDownRef) {
+        (data.dropDownRef as any).removeChild(
+          (data.dropDownRef as any).children[0]
+        );
+      }
+      data.dropDownRef = value;
+    };
+
+    const handleUploadingImageOrVideo = () => {
+      scrollToTarget('bottom');
+    };
+
+    const handleImagePreview = (message: any) => {
+      data.showImagePreview = !data.showImagePreview;
+      data.currentImagePreview = message;
+    };
+
+    const resetReplyOrReference = () => {
+      data.reference = {
+        message: {} as Message,
+        content: '',
+        type: 0,
+        show: ''
+      };
+    };
+
+    const handleTyping = (inputContentEmpty: boolean, inputBlur: boolean) => {
+      (data?.typingRef as any)?.onTyping(inputContentEmpty, inputBlur);
+    };
+
+    return {
+      ...toRefs(data),
+      conversationType,
+      messages,
+      messageEle,
+      inputEle,
+      messageInput,
+      messageAimID,
+      conversationData,
+      conversationName,
+      constant,
+      reportMessageSend,
+      handleTyping,
+      handleItem,
+      handleEdit,
+      handleEditor,
+      getHistoryMessageList,
+      handleApplication,
+      pluginComponentList,
+      handleSend,
+      closeDialog,
+      isMute,
+      pasting,
+      setMessageRead,
+      sendMessageReadReceipt,
+      dialog,
+      jumpID,
+      back,
+      slotDefault,
+      toggleshowGroupMemberList,
+      resendMessage,
+      submit,
+      Link,
+      openLink,
+      readReceiptDialog,
+      scrollToTarget,
+      needGroupReceipt,
+      handleDropDownOpen,
+      isMessageTip,
+      showDialog,
+      forwardMessage,
+      referOrReplyMessage,
+      handleUploadingImageOrVideo,
+      handleImagePreview,
+      imageList,
+      resetReplyOrReference
+    };
+  }
+});
+export default TUIChat;
+</script>
+
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 37 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/interface.ts

@@ -0,0 +1,37 @@
+interface Message {
+  ID?: string;
+  type?: string;
+  payload?: {
+    [propName: string]: any;
+  };
+  conversationID?: string;
+  conversationType?: string;
+  to?: string;
+  from?: string;
+  flow?: string;
+  time?: number;
+  status?: string;
+  isRevoked?: boolean;
+  priority?: string;
+  nick?: string;
+  avatar?: string;
+  isPeerRead?: boolean;
+  nameCard?: string;
+  atUserList?: Array<string>;
+  cloudCustomData?: string;
+  isDeleted?: boolean;
+  isModified?: boolean;
+  needReadReceipt?: boolean;
+  readReceiptInfo?: {
+    [propName: string]: any;
+  };
+  isBroadcastMessage?: boolean;
+}
+
+interface userListItem {
+  nick?: string;
+  avatar: string;
+  userID: string;
+}
+
+export { Message, userListItem };

+ 5 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/index.ts

@@ -0,0 +1,5 @@
+import Manage from './manage.vue';
+
+export {
+  Manage,
+};

+ 160 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage-member.vue

@@ -0,0 +1,160 @@
+<template>
+  <main class="member">
+    <ul class="list">
+      <li class="list-item" v-for="(item, index) in list" :key="index">
+        <aside @click="handleMemberProfileShow(item)">
+          <img
+            class="avatar"
+            :src="
+              item?.avatar ||
+              'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+            "
+            onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+          />
+          <span class="name">{{ item?.nick || item?.userID }}</span>
+          <span>{{ handleRoleName(item) }}</span>
+        </aside>
+        <i
+          v-if="item.role !== 'Owner' && isShowDel"
+          class="icon icon-del"
+          @click="submit(item)"
+        ></i>
+      </li>
+      <li class="list-item" v-if="list.length < total" @click="getMore">
+        {{ $t(`TUIChat.manage.查看更多`) }}
+      </li>
+    </ul>
+  </main>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+import TIM from '@tencentcloud/chat';
+
+const ManageMember = defineComponent({
+  components: {},
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    },
+    total: {
+      type: Number,
+      default: () => 0
+    },
+    isShowDel: {
+      type: Boolean,
+      default: () => false
+    },
+    self: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const types: any = TIM.TYPES;
+    const data: any = reactive({
+      total: 0,
+      list: [],
+      isShowDel: false,
+      self: {}
+    });
+
+    watchEffect(() => {
+      data.total = props.total;
+      data.isShowDel = props.isShowDel;
+      data.list = props.list;
+      data.self = props.self;
+    });
+
+    const handleRoleName = (item: any) => {
+      const { t } = (window as any).TUIKitTUICore.config.i18n.useI18n();
+      let name = '';
+      switch (item?.role) {
+        case types.GRP_MBR_ROLE_ADMIN:
+          name = t('TUIChat.manage.管理员');
+          break;
+        case types.GRP_MBR_ROLE_OWNER:
+          name = t('TUIChat.manage.群主');
+          break;
+      }
+      if (name) {
+        name = `(${name})`;
+      }
+      if (item.userID === data.self.userID) {
+        name += ` (${t('TUIChat.manage.我')})`;
+      }
+      return name;
+    };
+
+    const getMore = () => {
+      ctx.emit('more');
+    };
+
+    const submit = (item: any) => {
+      ctx.emit('del', [item]);
+    };
+
+    const handleMemberProfileShow = (user: any) => {
+      ctx.emit('handleMemberProfileShow', user);
+    };
+
+    return {
+      ...toRefs(data),
+      getMore,
+      submit,
+      handleRoleName,
+      handleMemberProfileShow
+    };
+  }
+});
+export default ManageMember;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.member {
+  flex: 1;
+  background: #ffffff;
+  .list {
+    display: flex;
+    flex-direction: column;
+    background: #f4f5f9;
+    padding-top: 12Px;
+    &-item {
+      padding: 13Px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      background: #ffffff;
+      font-size: 14Px;
+      overflow: hidden;
+      &:hover {
+        background: #f1f2f6;
+      }
+      aside {
+        display: flex;
+        align-items: center;
+        width: 100%;
+        overflow: hidden;
+        .name {
+          padding-left: 8Px;
+          font-weight: 400;
+          font-size: 14Px;
+          color: #000000;
+          flex: 1;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+  }
+}
+.avatar {
+  width: 36Px;
+  height: 36Px;
+  border-radius: 4Px;
+}
+</style>

+ 207 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage-name.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="name">
+    <label>{{ $t(`TUIChat.manage.群名称`) }}</label>
+    <div v-if="isEdit" :class="[isH5 ? 'edit-h5' : '']" ref="dialog">
+      <main>
+        <header class="edit-h5-header" v-if="isH5">
+          <aside class="left">
+            <h1>{{ $t(`TUIChat.manage.修改群聊名称`) }}</h1>
+            <span>
+              {{ $t(`TUIChat.manage.修改群聊名称后,将在群内通知其他成员`) }}
+            </span>
+          </aside>
+          <span class="close" @click="toggleEdit">{{ $t(`关闭`) }}</span>
+        </header>
+        <div class="input-box">
+          <input
+            class="input"
+            v-if="isEdit"
+            v-model="input"
+            type="text"
+            @keyup.enter="updateProfile"
+          />
+          <span v-if="isH5">
+            {{ $t(`TUIChat.manage.仅限中文、字母、数字和下划线,2-20个字`) }}
+          </span>
+        </div>
+        <footer class="edit-h5-footer" v-if="isH5">
+          <button class="btn" :disabled="!input" @click="updateProfile">
+            {{ $t(`确认`) }}
+          </button>
+        </footer>
+      </main>
+    </div>
+    <p v-if="!isEdit || isH5" @click="toggleEdit">
+      <span>{{ groupProfile.name }}</span>
+      <i class="icon icon-edit" v-if="isAuth"></i>
+    </p>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs, ref } from 'vue';
+import { onClickOutside } from '@vueuse/core';
+
+const manageName = defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    isAuth: {
+      type: Boolean,
+      default: false
+    },
+    isH5: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data: any = reactive({
+      groupProfile: {},
+      input: '',
+      isEdit: false
+    });
+
+    watchEffect(() => {
+      data.groupProfile = props.data;
+    });
+
+    const dialog: any = ref();
+
+    onClickOutside(dialog, () => {
+      data.isEdit = false;
+    });
+
+    const updateProfile = async () => {
+      if (data.input && data.input !== data.groupProfile.name) {
+        ctx.emit('update', { key: 'name', value: data.input });
+        data.groupProfile.name = data.input;
+        data.input = '';
+      }
+      toggleEdit();
+    };
+
+    const toggleEdit = async () => {
+      if (props.isAuth) {
+        data.isEdit = !data.isEdit;
+      }
+      if (data.isEdit) {
+        data.input = data.groupProfile.name;
+      }
+    };
+
+    return {
+      ...toRefs(data),
+      updateProfile,
+      toggleEdit,
+      dialog
+    };
+  }
+});
+export default manageName;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.name {
+  padding: 14Px 20Px;
+  font-weight: 400;
+  font-size: 14Px;
+  color: #000000;
+  display: flex;
+  flex-direction: column;
+  p {
+    opacity: 0.6;
+    display: flex;
+    align-items: center;
+    .icon {
+      margin-left: 4Px;
+    }
+  }
+}
+.input-box {
+  display: flex;
+  .input {
+    flex: 1;
+    border: 1Px solid #e8e8e9;
+    border-radius: 4Px;
+    padding: 4Px 16Px;
+    font-weight: 400;
+    font-size: 14Px;
+    color: #000000;
+    opacity: 0.6;
+  }
+}
+
+.space-top {
+  border-top: 10Px solid #f4f5f9;
+}
+.edit-h5 {
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: flex-end;
+  z-index: 1;
+  main {
+    background: #ffffff;
+    flex: 1;
+    padding: 18Px;
+    border-radius: 12Px 12Px 0 0;
+    .input-box {
+      flex-direction: column;
+      padding: 18Px 0;
+      .input {
+        background: #f8f8f8;
+        padding: 10Px 12Px;
+      }
+      span {
+        font-family: PingFangSC-Regular;
+        font-weight: 400;
+        font-size: 12Px;
+        color: #888888;
+        letter-spacing: 0;
+        padding-top: 8Px;
+      }
+    }
+  }
+  &-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .close {
+      font-family: PingFangSC-Regular;
+      font-weight: 400;
+      font-size: 18Px;
+      color: #3370ff;
+      letter-spacing: 0;
+      line-height: 27Px;
+    }
+  }
+  &-footer {
+    display: flex;
+    .btn {
+      flex: 1;
+      border: none;
+      background: #147aff;
+      border-radius: 5Px;
+      font-family: PingFangSC-Regular;
+      font-weight: 400;
+      font-size: 16Px;
+      color: #ffffff;
+      letter-spacing: 0;
+      line-height: 27Px;
+      padding: 8Px 0;
+      &:disabled {
+        opacity: 0.3;
+      }
+    }
+  }
+}
+</style>

+ 116 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage-notification.vue

@@ -0,0 +1,116 @@
+<template>
+  <main class="notification">
+    <textarea
+      v-if="isEdit"
+      v-model="input"
+      @keyup.enter="updateProfile"
+    ></textarea>
+    <section v-else>
+      <p v-if="!groupProfile.notification">
+        {{ $t(`TUIChat.manage.暂无公告`) }}
+      </p>
+      <article v-else>{{ groupProfile.notification }}</article>
+    </section>
+    <footer v-if="isAuth">
+      <button class="btn" v-if="isEdit" @click="updateProfile">
+        {{ $t(`TUIChat.manage.发布`) }}
+      </button>
+      <button class="btn" v-else @click="isEdit = !isEdit">
+        {{ $t(`TUIChat.manage.编辑`) }}
+      </button>
+    </footer>
+  </main>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, reactive, toRefs } from 'vue';
+
+const ManageNotification = defineComponent({
+  props: {
+    data: {
+      type: Object,
+      default: () => ({})
+    },
+    isAuth: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data: any = reactive({
+      groupProfile: {},
+      input: '',
+      isEdit: false
+    });
+
+    watchEffect(() => {
+      data.groupProfile = props.data;
+      data.input = data.groupProfile.notification;
+    });
+
+    // 更新群资料
+    const updateProfile = async () => {
+      if (data.input && data.input !== data.groupProfile.notification) {
+        ctx.emit('update', { key: 'notification', value: data.input });
+        data.groupProfile.notification = data.input;
+        data.input = '';
+      }
+      data.isEdit = !data.isEdit;
+    };
+
+    return {
+      ...toRefs(data),
+      updateProfile
+    };
+  }
+});
+export default ManageNotification;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.notification {
+  flex: 1;
+  padding: 20Px;
+  display: flex;
+  flex-direction: column;
+  section {
+    flex: 1;
+    font-size: 14Px;
+    p {
+      text-align: center;
+      padding-bottom: 20Px;
+    }
+  }
+  textarea {
+    margin-bottom: 20Px;
+    flex: 1;
+    box-sizing: border-box;
+    padding: 10Px;
+    border: 1Px solid #e8e8e9;
+    resize: none;
+    font-size: 14Px;
+  }
+  footer {
+    display: flex;
+    justify-content: flex-end;
+    padding: 10Px;
+  }
+}
+.btn {
+  background: #3370ff;
+  border: 0 solid #2f80ed;
+  padding: 4Px 28Px;
+  font-weight: 400;
+  font-size: 12Px;
+  color: #ffffff;
+  line-height: 24Px;
+  border-radius: 4Px;
+  &-cancel {
+    background: #ffffff;
+    border: 1Px solid #dddddd;
+    color: #828282;
+  }
+}
+</style>

+ 880 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/manage.vue

@@ -0,0 +1,880 @@
+<template>
+  <div>
+    <i class="icon icon-chat-setting" @click="toggleShow"></i>
+    <div
+      class="manage"
+      :class="[isH5 ? 'manage-h5' : '']"
+      v-if="show"
+      ref="dialog"
+    >
+      <header class="manage-header">
+        <i
+          class="icon icon-back"
+          v-if="isH5 && !currentTab"
+          @click="toggleShow"
+        ></i>
+        <aside class="manage-header-left">
+          <i class="icon icon-back" v-if="currentTab" @click="setTab('')"></i>
+          <main>
+            <h1>{{ $t(`TUIChat.manage.${TabName}`) }}</h1>
+          </main>
+        </aside>
+        <span>
+          <i v-if="!isH5" class="icon icon-close" @click="toggleShow"></i>
+        </span>
+      </header>
+      <main class="main" style="background-color: #f4f5f9; height: 100%;" v-if="!currentTab">
+        <ManageName
+          class="space-top"
+          :isAuth="isAuth"
+          :isH5="isH5"
+          :data="conversation.groupProfile"
+          @update="updateProfile"
+          style="background-color: #fff;"
+        />
+        <div class="userInfo space-top" style="background-color: #fff;">
+          <header class="userInfo-header" @click="setTab('member')">
+            <label>{{ $t(`TUIChat.manage.群成员`) }}</label>
+            <p>
+              <span>
+                {{ conversation.groupProfile.memberCount
+                }}{{ $t(`TUIChat.manage.人`) }}
+              </span>
+              <i class="icon icon-right"></i>
+            </p>
+          </header>
+          <ol>
+            <dl
+              v-for="(item, index) in userInfo?.list?.slice(0, showUserNum)"
+              :key="index"
+            >
+              <dt @click="handleMemberProfileShow(item)">
+                <img
+                  class="avatar"
+                  :src="
+                    item?.avatar ||
+                    'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+                  "
+                  onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+                />
+              </dt>
+              <dd>{{ item?.nick || item?.userID }}</dd>
+            </dl>
+            <dl v-if="isShowAddMember">
+              <dt class="avatar" @click="toggleMask('add')">+</dt>
+            </dl>
+            <dl v-if="conversation.groupProfile.selfInfo.role === 'Owner'">
+              <dt class="avatar" @click="toggleMask('remove')">-</dt>
+            </dl>
+          </ol>
+        </div>
+        <ul class="content space-top" @click="editLableName = ''" style="background-color: #fff;">
+          <li @click.stop="setTab('notification')">
+            <aside>
+              <label>{{ $t(`TUIChat.manage.群公告`) }}</label>
+              <article>{{ conversation.groupProfile.notification }}</article>
+            </aside>
+            <i class="icon icon-right end"></i>
+          </li>
+          <!-- <li v-if="isAdmin && isSetMuteTime" @click.stop="setTab('admin')">
+            <label>{{ $t(`TUIChat.manage.群管理`) }}</label>
+            <i class="icon icon-right"></i>
+          </li>
+          <li>
+            <label>{{ $t(`TUIChat.manage.群ID`) }}</label>
+            <div class="groupID">
+              <span>{{ conversation.groupProfile.groupID }}</span>
+              <i
+                class="icon icon-msg-copy"
+                @click="handleGroupIDCopy"
+                :title="$t('TUIChat.复制')"
+              ></i>
+            </div>
+          </li>
+          <li>
+            <label>{{ $t(`TUIChat.manage.群头像`) }}</label>
+            <img
+              class="avatar"
+              :src="
+                conversation?.groupProfile?.avatar ||
+                'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690775328089.png'
+              "
+              onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690775328089.png'"
+            />
+          </li>
+          <li>
+            <label>{{ $t(`TUIChat.manage.群类型`) }}</label>
+            <span>
+              {{
+                $t(`TUIChat.manage.${typeName[conversation.groupProfile.type]}`)
+              }}
+            </span>
+          </li>
+          <li>
+            <label>{{ $t(`TUIChat.manage.加群方式`) }}</label>
+            <span>
+              {{
+                $t(
+                  `TUIChat.manage.${
+                    typeName[conversation.groupProfile.joinOption]
+                  }`
+                )
+              }}
+            </span>
+          </li> -->
+        </ul>
+        <ul class="footer space-top">
+          <li
+            v-if="
+              conversation.groupProfile.selfInfo.role === 'Owner' &&
+              userInfo?.list.length > 1
+            "
+            @click.stop="toggleMask('changeOwner')"
+          >
+            {{ $t(`TUIChat.manage.转让群组`) }}
+          </li>
+          <li
+            v-if="!!isDismissGroupAuth"
+            @click.stop="dismiss(conversation.groupProfile)"
+          >
+            {{ $t(`TUIChat.manage.解散群聊`) }}
+          </li>
+          <!-- <li v-else @click.stop="quit(conversation.groupProfile)">
+            {{ $t(`TUIChat.manage.退出群组`) }}
+          </li> -->
+        </ul>
+      </main>
+      <ManageMember
+        v-else-if="currentTab === 'member'"
+        :self="conversation.groupProfile.selfInfo"
+        :list="userInfo.list"
+        :total="~~conversation.groupProfile.memberCount"
+        :isShowDel="conversation.groupProfile.selfInfo.role === 'Owner'"
+        @more="getMember('more')"
+        @del="submit"
+        @handleMemberProfileShow="handleMemberProfileShow"
+      />
+      <MemeberProfile
+        v-else-if="currentTab === 'profile'"
+        :userInfo="currentMember"
+      />
+      <ManageNotification
+        v-else-if="currentTab === 'notification'"
+        :isAuth="isAuth"
+        :data="conversation.groupProfile"
+        @update="updateProfile"
+      />
+      <main class="admin" v-else-if="currentTab === 'admin'">
+        <div class="admin-list" v-if="isAdmin">
+          <label>{{ $t(`TUIChat.manage.群管理员`) }}</label>
+          <ol>
+            <dl v-for="(item, index) in member.admin" :key="index">
+              <dt>
+                <img
+                  class="avatar"
+                  :src="
+                    item?.avatar ||
+                    'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+                  "
+                  onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+                />
+              </dt>
+              <dd>{{ item?.nick || item?.userID }}</dd>
+            </dl>
+            <dl>
+              <dt class="avatar" @click="toggleMask('addAdmin')">+</dt>
+            </dl>
+            <dl>
+              <dt
+                class="avatar"
+                v-if="member.admin.length > 0"
+                @click="toggleMask('removeAdmin')"
+              >
+                -
+              </dt>
+            </dl>
+          </ol>
+        </div>
+        <div class="admin-content space-top" v-if="isSetMuteTime">
+          <aside>
+            <label>{{ $t(`TUIChat.manage.全员禁言`) }}</label>
+            <p>
+              {{
+                $t(`TUIChat.manage.全员禁言开启后,只允许群主和管理员发言。`)
+              }}
+            </p>
+          </aside>
+          <Slider
+            :open="conversation.groupProfile.muteAllMembers"
+            @change="setAllMuteTime"
+          />
+        </div>
+        <div class="admin-list last" v-if="isSetMuteTime">
+          <label>{{ $t(`TUIChat.manage.单独禁言人员`) }}</label>
+          <ol>
+            <dl v-for="(item, index) in member.muteMember" :key="index">
+              <dt>
+                <img
+                  class="avatar"
+                  :src="
+                    item?.avatar ||
+                    'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+                  "
+                  onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+                />
+              </dt>
+              <dd>{{ item?.nick || item?.userID }}</dd>
+            </dl>
+            <dl>
+              <dt class="avatar" @click="toggleMask('addMute')">+</dt>
+            </dl>
+            <dl>
+              <dt
+                class="avatar"
+                v-if="member.muteMember.length > 0"
+                @click="toggleMask('removeMute')"
+              >
+                -
+              </dt>
+            </dl>
+          </ol>
+        </div>
+      </main>
+      <MaskTUI :show="mask" @update:show="e => (mask = e)">
+        <Transfer
+          :title="$t(`TUIChat.manage.${transferTitle}`)"
+          :list="transferList"
+          :isSearch="isSearch"
+          :isRadio="isRadio"
+          :selectedList="selectedList"
+          @submit="submit"
+          @cancel="cancel"
+          @search="handleSearchMember"
+          :isH5="isH5"
+        />
+      </MaskTUI>
+      <DialogTUI
+        :title="$t(`TUIChat.manage.删除成员`)"
+        :show="delDialogShow"
+        :isH5="isH5"
+        :center="true"
+        :isHeaderShow="!isH5"
+        @submit="handleManage(userList, 'remove')"
+        @update:show="e => (delDialogShow = e)"
+      >
+        <p v-if="userList.length === 1" class="delDialog-title">
+          {{ $t(`TUIChat.manage.确定从群聊中删除该成员?`) }}
+        </p>
+        <p v-if="userList.length > 1" class="delDialog-title">
+          {{ $t(`TUIChat.manage.确定从群聊中删除所选成员?`) }}
+        </p>
+      </DialogTUI>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  watchEffect,
+  reactive,
+  toRefs,
+  computed,
+  watch,
+  ref
+} from 'vue';
+import { onClickOutside } from '@vueuse/core';
+import MaskTUI from '../../../components/mask/mask.vue';
+import Transfer from '../../../components/transfer/index.vue';
+import Slider from '../../../components/slider/index.vue';
+import ManageName from './manage-name.vue';
+import ManageNotification from './manage-notification.vue';
+import ManageMember from './manage-member.vue';
+import MemeberProfile from './member-profile.vue';
+import DialogTUI from '../../../components/dialog/index.vue';
+
+import Vuex from 'vuex';
+import { handleErrorPrompts } from '../../utils';
+import useClipboard from 'vue-clipboard3';
+
+const manage = defineComponent({
+  components: {
+    MaskTUI,
+    Transfer,
+    Slider,
+    ManageName,
+    ManageNotification,
+    ManageMember,
+    MemeberProfile,
+    DialogTUI
+  },
+  props: {
+    userInfo: {
+      type: Object,
+      default: () => ({
+        isGroup: false,
+        list: []
+      })
+    },
+    conversation: {
+      type: Object,
+      default: () => ({})
+    },
+    show: {
+      type: Boolean,
+      default: () => false
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const types: any = manage.TUIServer.TUICore.TIM.TYPES;
+    const { GroupServer } = manage;
+    const { t } = manage.TUIServer.TUICore.config.i18n.useI18n();
+    const data: any = reactive({
+      conversation: {},
+      userInfo: {
+        isGroup: false,
+        list: []
+      },
+      isShowMuteTimeInput: false,
+      editLableName: '',
+      mask: false,
+      currentTab: '',
+      transferType: '',
+      isSearch: false,
+      isRadio: false,
+      transferList: [],
+      selectedList: [],
+      isMuteTime: false,
+      show: false,
+      typeName: {
+        [types.GRP_WORK]: '好友工作群',
+        [types.GRP_PUBLIC]: '陌生人社交群',
+        [types.GRP_MEETING]: '临时会议群',
+        [types.GRP_AVCHATROOM]: '直播群',
+        [types.JOIN_OPTIONS_FREE_ACCESS]: '自由加入',
+        [types.JOIN_OPTIONS_NEED_PERMISSION]: '需要验证',
+        [types.JOIN_OPTIONS_DISABLE_APPLY]: '禁止加群'
+      },
+      delDialogShow: false,
+      userList: [],
+      transferTitle: '',
+      member: {
+        admin: [],
+        member: [],
+        muteMember: []
+      },
+      currentMember: {}
+    });
+
+    const dialog: any = ref();
+
+    watchEffect(() => {
+      data.conversation = props.conversation;
+      data.userInfo = props.userInfo;
+      data.show = props.show;
+    });
+
+    const VuexStore =
+      ((window as any)?.TUIKitTUICore?.isOfficial &&
+        (Vuex as any)?.useStore()) ||
+      {};
+
+    const TabName = computed(() => {
+      let name = '';
+      switch (data.currentTab) {
+        case 'notification':
+          name = '群公告';
+          break;
+        case 'member':
+          name = '群成员';
+          break;
+        case 'profile':
+          name = '群成员';
+          break;
+        default:
+          name = '群管理';
+          break;
+      }
+      return name;
+    });
+
+    watch(
+      () => data.userInfo.list,
+      (newValue: any, oldValue: any) => {
+        data.member = {
+          admin: [],
+          member: [],
+          muteMember: []
+        };
+        newValue.map((item: any) => {
+          switch (item?.role) {
+            case types.GRP_MBR_ROLE_ADMIN:
+              data.member.admin.push(item);
+              break;
+            case types.GRP_MBR_ROLE_MEMBER:
+              data.member.member.push(item);
+              break;
+            default:
+              break;
+          }
+          return item;
+        });
+        const time: number = new Date().getTime();
+        data.member.muteMember = newValue.filter(
+          (item: any) => item?.muteUntil * 1000 - time > 0
+        );
+      },
+      { deep: true }
+    );
+
+    const isDismissGroupAuth = computed(() => {
+      const { conversation } = data;
+      const userRole = conversation?.groupProfile?.selfInfo.role;
+      const groupType = conversation?.groupProfile?.type;
+
+      const isOwner = userRole === types.GRP_MBR_ROLE_OWNER;
+      const isWork = groupType === types.GRP_WORK;
+
+      return isOwner && !isWork;
+    });
+
+    const isShowAddMember = computed(() => {
+      const { conversation } = data;
+      const groupType = conversation?.groupProfile?.type;
+      const isWork = groupType === types.GRP_WORK;
+
+      if (isWork) {
+        return true;
+      }
+      return false;
+    });
+
+    const showUserNum = computed(() => {
+      let num = 3;
+      if (!isShowAddMember.value) {
+        num += 1;
+      }
+      if ((data.conversation as any).groupProfile.selfInfo.role !== 'Owner') {
+        num += 1;
+      }
+      return num;
+    });
+
+    const isAuth = computed(() => {
+      const { conversation } = data;
+      const userRole = conversation?.groupProfile?.selfInfo.role;
+
+      const isOwner = userRole === types.GRP_MBR_ROLE_OWNER;
+      const isAdmin = userRole === types.GRP_MBR_ROLE_ADMIN;
+
+      return isOwner || isAdmin;
+    });
+
+    const isAdmin = computed(() => {
+      const { conversation } = data;
+      const groupType = conversation?.groupProfile?.type;
+      const userRole = conversation?.groupProfile?.selfInfo.role;
+
+      const isOwner = userRole === types.GRP_MBR_ROLE_OWNER;
+      const isWork = groupType === types.GRP_WORK;
+      const isAVChatRoom = groupType === types.GRP_AVCHATROOM;
+
+      if (!isWork && !isAVChatRoom && isOwner) {
+        return true;
+      }
+      return false;
+    });
+
+    const isSetMuteTime = computed(() => {
+      const { conversation } = data;
+      const groupType = conversation?.groupProfile?.type;
+      const isWork = groupType === types.GRP_WORK;
+
+      if (isWork || !isAuth.value) {
+        return false;
+      }
+      return true;
+    });
+
+    const getMember = (type?: string) => {
+      const { conversation } = data;
+      const options: any = {
+        groupID: conversation?.groupProfile?.groupID,
+        count: 100,
+        offset: type && type === 'more' ? data.userInfo.list.length : 0
+      };
+      GroupServer.getGroupMemberList(options).then((res: any) => {
+        if (type && type === 'more') {
+          data.userInfo.list = [...data.userInfo.list, ...res.data.memberList];
+        } else {
+          data.userInfo.list = res.data.memberList;
+        }
+      });
+    };
+
+    const addMember = async (userIDList: any) => {
+      const { conversation } = data;
+      const options: any = {
+        groupID: conversation.groupProfile.groupID,
+        userIDList
+      };
+      await GroupServer.addGroupMember(options);
+      getMember('More');
+    };
+
+    const deleteMember = (user: any) => {
+      const { conversation } = data;
+      const options: any = {
+        groupID: conversation.groupProfile.groupID,
+        userIDList: [user.userID]
+      };
+      GroupServer.deleteGroupMember(options);
+    };
+
+    const changeOwner = async (userID: any) => {
+      const options: any = {
+        groupID: data.conversation.groupProfile.groupID,
+        newOwnerID: userID
+      };
+      const imResponse = await GroupServer.changeGroupOwner(options);
+      data.conversation.groupProfile = {};
+      data.conversation.groupProfile = imResponse.data.group;
+    };
+
+    const quit = async (group: any) => {
+      await GroupServer.quitGroup(group.groupID);
+      manage.TUIServer.store.conversation = {};
+    };
+
+    const dismiss = async (group: any) => {
+      await GroupServer.dismissGroup(group.groupID);
+      manage.TUIServer.store.conversation = {};
+      (window as any)?.TUIKitTUICore?.isOfficial &&
+        VuexStore?.commit &&
+        VuexStore?.commit('handleTask', 5);
+    };
+
+    const handleAdmin = async (user: any) => {
+      const { conversation } = data;
+      let role = '';
+      switch (user.role) {
+        case types.GRP_MBR_ROLE_ADMIN:
+          role = types.GRP_MBR_ROLE_MEMBER;
+          break;
+        case types.GRP_MBR_ROLE_MEMBER:
+          role = types.GRP_MBR_ROLE_ADMIN;
+          break;
+      }
+      const options: any = {
+        groupID: conversation.groupProfile.groupID,
+        userID: user.userID,
+        role
+      };
+      await GroupServer.setGroupMemberRole(options);
+      getMember();
+    };
+
+    const setMemberMuteTime = async (userID: string, type?: string) => {
+      const { conversation } = data;
+      const options: any = {
+        groupID: conversation.groupProfile.groupID,
+        userID,
+        muteTime: type === 'add' ? 60 * 60 * 24 * 30 : 0
+      };
+      await GroupServer.setGroupMemberMuteTime(options);
+      if (type === 'add') {
+        (window as any)?.TUIKitTUICore?.isOfficial &&
+          VuexStore?.commit &&
+          VuexStore?.commit('handleTask', 4);
+      }
+      getMember();
+    };
+
+    const kickedOut = async (userIDList: any) => {
+      const { conversation } = data;
+      const options: any = {
+        groupID: conversation.groupProfile.groupID,
+        userIDList,
+        reason: ''
+      };
+      await GroupServer.deleteGroupMember(options);
+      getMember();
+    };
+
+    const edit = (labelName: string) => {
+      data.editLableName = labelName;
+    };
+
+    const updateProfile = async (params: any) => {
+      const { key, value } = params;
+      const options: any = {
+        groupID: data.conversation.groupProfile.groupID,
+        [key]: value
+      };
+      const res = await GroupServer.updateGroupProfile(options);
+      const { conversation } = manage.TUIServer.store;
+      conversation.groupProfile = res.data.group;
+      manage.TUIServer.store.conversation = {};
+      manage.TUIServer.store.conversation = conversation;
+      data.editLableName = '';
+    };
+
+    const setTab = (tabName: string) => {
+      data.currentTab = tabName;
+      data.editLableName = '';
+      if (data.currentTab === 'member') {
+        data.transferType = 'remove';
+      }
+      if (!data.currentTab) {
+        data.transferType = '';
+      }
+    };
+
+    const handleSearchMember = async (value: string) => {
+      let imResponse: any = {};
+      let imMemberResponse: any = {};
+      const options: any = {
+        groupID: data.conversation.groupProfile.groupID,
+        userIDList: [value]
+      };
+      switch (data.transferType) {
+        case 'add':
+          try {
+            imMemberResponse = await GroupServer.getGroupMemberProfile(options);
+            data.transferList = data.transferList.filter(
+              (item: any) => item.userID !== imResponse.data[0]?.userID
+            );
+            data.transferList = [...data.transferList, ...imResponse.data];
+            if (imMemberResponse?.data?.memberList.length > 0) {
+              data.transferList = data.transferList.map((item: any) => {
+                if (
+                  item.userID === imMemberResponse?.data?.memberList[0].userID
+                ) {
+                  item.isDisabled = true;
+                }
+                return item;
+              });
+            }
+          } catch (error) {
+            const message = t('TUIChat.manage.该用户不存在');
+            handleErrorPrompts(message, props);
+          }
+          break;
+        case 'remove':
+          try {
+            imResponse = await GroupServer.getGroupMemberProfile(options);
+            if (imResponse.data.memberList.length === 0) {
+              const message = t('TUIChat.manage.该用户不在群组内');
+              return handleErrorPrompts(message, props);
+            }
+            data.transferList = data.transferList.filter(
+              (item: any) =>
+                // eslint-disable-next-line no-unsafe-optional-chaining
+                item.userID !== imResponse?.data?.memberList[0]?.userID
+            );
+            data.transferList = [
+              ...data.transferList,
+              // eslint-disable-next-line no-unsafe-optional-chaining
+              ...imResponse?.data?.memberList
+            ];
+          } catch (error) {
+            const message = t('TUIChat.manage.该用户不存在');
+            handleErrorPrompts(message, props);
+          }
+          break;
+        default:
+          break;
+      }
+    };
+
+    const submit = (userList: any) => {
+      if (data.transferType === 'remove') {
+        data.userList = userList;
+        data.delDialogShow = !data.delDialogShow;
+      } else {
+        handleManage(userList, data.transferType);
+      }
+      data.mask = false;
+    };
+
+    const friendList = async () => {
+      const imResponse = await manage.TUIServer.getFriendList();
+      const friendList = imResponse.data.map((item: any) => item?.profile);
+      return friendList.filter(
+        (item: any) =>
+          !data.userInfo.list.some(
+            (infoItem: any) => infoItem.userID === item.userID
+          )
+      );
+    };
+
+    const cancel = () => {
+      toggleMask();
+    };
+
+    const toggleMask = async (type?: string) => {
+      data.selectedList = [];
+      switch (type) {
+        case 'add':
+          data.isRadio = false;
+          data.transferList = await friendList();
+          data.transferTitle = '添加成员';
+          break;
+        case 'remove':
+          data.isRadio = false;
+          data.transferList = data.userInfo.list.filter(
+            (item: any) =>
+              // eslint-disable-next-line no-unsafe-optional-chaining
+              item.userID !== data.conversation?.groupProfile?.selfInfo.userID
+          );
+          data.transferTitle = '删除成员';
+          break;
+        case 'addAdmin':
+          data.isRadio = true;
+          data.transferList = data.member.member;
+          data.transferTitle = '新增管理员';
+          break;
+        case 'removeAdmin':
+          data.isRadio = true;
+          data.transferList = data.member.admin;
+          data.transferTitle = '移除管理员';
+          break;
+        case 'changeOwner':
+          data.isRadio = true;
+          data.transferList = [...data.member.admin, ...data.member.member];
+          data.transferTitle = '转让群组';
+          break;
+        case 'addMute':
+          data.isRadio = true;
+          data.transferList = data.member.member;
+          if (data.conversation.groupProfile.selfInfo.role === 'Owner') {
+            data.transferList = [...data.member.admin, ...data.member.member];
+          }
+          data.transferTitle = '新增禁言用户';
+          break;
+        case 'removeMute':
+          data.isRadio = true;
+          data.transferList = data.member.muteMember;
+          data.transferTitle = '移除禁言用户';
+          break;
+        default:
+          break;
+      }
+      data.transferType = type;
+      data.mask = !data.mask;
+    };
+
+    onClickOutside(dialog, () => {
+      data.show = false;
+    });
+
+    const toggleShow = () => {
+      if (!GroupServer) {
+        const message = t('TUIChat.manage.请先注册 TUIGroup 模块');
+        return handleErrorPrompts(message, props);
+      }
+      data.show = !data.show;
+      if (!data.show) {
+        data.currentTab = '';
+      }
+      if (data.show) {
+        getMember();
+      }
+    };
+
+    const setAllMuteTime = (value: boolean) => {
+      updateProfile({ key: 'muteAllMembers', value });
+      (window as any)?.TUIKitTUICore?.isOfficial &&
+        VuexStore?.commit &&
+        VuexStore?.commit('handleTask', 4);
+    };
+
+    const handleManage = (userList: any, type: any) => {
+      const userIDList: any = [];
+      userList.map((item: any) => {
+        userIDList.push(item.userID);
+        return item;
+      });
+      switch (type) {
+        case 'add':
+          addMember(userIDList);
+          break;
+        case 'remove':
+          kickedOut(userIDList);
+          break;
+        case 'addAdmin':
+          handleAdmin(userList[0]);
+          break;
+        case 'removeAdmin':
+          handleAdmin(userList[0]);
+          break;
+        case 'changeOwner':
+          changeOwner(userIDList[0]);
+          break;
+        case 'addMute':
+          setMemberMuteTime(userIDList[0], 'add');
+          break;
+        case 'removeMute':
+          setMemberMuteTime(userIDList[0], 'remove');
+          break;
+        default:
+          break;
+      }
+    };
+
+    const handleGroupIDCopy = async () => {
+      try {
+        const { toClipboard } = useClipboard();
+        await toClipboard(data?.conversation?.groupProfile?.groupID);
+      } catch (error) {
+        handleErrorPrompts(error, data.env);
+      }
+    };
+
+    const handleMemberProfileShow = (user: any) => {
+      data.currentMember = user;
+      setTab('profile');
+    };
+
+    return {
+      ...toRefs(data),
+      isDismissGroupAuth,
+      isShowAddMember,
+      isSetMuteTime,
+      isAdmin,
+      isAuth,
+      addMember,
+      deleteMember,
+      changeOwner,
+      quit,
+      dismiss,
+      handleAdmin,
+      setMemberMuteTime,
+      kickedOut,
+      edit,
+      updateProfile,
+      setTab,
+      TabName,
+      getMember,
+      handleSearchMember,
+      submit,
+      cancel,
+      toggleMask,
+      toggleShow,
+      setAllMuteTime,
+      handleManage,
+      showUserNum,
+      dialog,
+      handleGroupIDCopy,
+      handleMemberProfileShow
+    };
+  }
+});
+export default manage;
+</script>
+
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 160 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/member-profile.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="memeber-profile">
+    <div class="memeber-profile-main">
+      <img
+        class="avatar"
+        :src="
+          userInfo?.avatar ||
+          'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+        "
+        onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+      />
+      <ul class="list">
+        <h1>{{ userInfo?.nick || userInfo?.userID }}</h1>
+        <li>
+          <label>ID:</label>
+          <span>{{ userInfo?.userID }}</span>
+        </li>
+        <li>
+          <label>{{ $t('TUIContact.个性签名') }}:</label>
+          <span>{{ userInfo?.selfSignature }}</span>
+        </li>
+      </ul>
+    </div>
+    <div class="memeber-profile-footer">
+      <div
+        class="button"
+        @click="enter(userInfo?.userID, 'C2C')"
+        v-if="showEnter()"
+      >
+        {{ $t('TUIContact.发送消息') }}
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import TIM from '../../../../TUICore/tim';
+import { reactive, toRefs, watch, watchEffect, defineComponent } from 'vue';
+import manage from './manage.vue';
+
+const memberProfile = defineComponent({
+  props: {
+    userInfo: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props: any, ctx: any) {
+    const TUIServer = manage?.TUIServer;
+    const data = reactive({
+      isFriendShip: false,
+      userInfo: {},
+      self: {}
+    });
+    watchEffect(() => {
+      data.self = props.self;
+    });
+    watch(
+      () => props.userInfo,
+      async (newVal: any, oldVal: any) => {
+        if (newVal === oldVal) return;
+        const res = await TUIServer.getUserProfile([props?.userInfo?.userID]);
+        data.userInfo = res?.data[0];
+        checkFriend();
+      },
+      {
+        deep: true,
+        immediate: true
+      }
+    );
+    const enter = async (ID: any, type: string) => {
+      const name = `${type}${ID}`;
+      TUIServer.TUICore.TUIServer.TUIConversation.getConversationProfile(
+        name
+      ).then((imResponse: any) => {
+        // 通知 TUIConversation 添加当前会话
+        // Notify TUIConversation to toggle the current conversation
+        TUIServer.TUICore.TUIServer.TUIConversation.handleCurrentConversation(
+          imResponse.data.conversation
+        );
+      });
+    };
+    const checkFriend = async () => {
+      if (!(data.userInfo as any).userID) return;
+      const relation = await TUIServer.checkFriend(
+        (data.userInfo as any).userID,
+        TIM.TYPES.SNS_CHECK_TYPE_BOTH
+      );
+      data.isFriendShip =
+        relation === TIM.TYPES.SNS_TYPE_BOTH_WAY ? true : false;
+    };
+
+    const showEnter = () => {
+      return data.isFriendShip || !TUIServer?.TUICore?.isOfficial;
+    };
+    return {
+      ...toRefs(data),
+      enter,
+      showEnter
+    };
+  }
+});
+export default memberProfile;
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.memeber-profile {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  &-main {
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+    overflow: hidden;
+    img {
+      width: 60Px;
+      height: 60Px;
+      border-radius: 8Px;
+      margin: 20Px 10Px 20Px 20Px;
+    }
+    .list {
+      flex: 1;
+      overflow: hidden;
+      margin: 20Px 10Px;
+      font-weight: 400;
+      li {
+        color: #999999;
+      }
+      h1,
+      li {
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+      }
+    }
+  }
+  &-footer {
+    border-top: 10Px solid #f4f5f9;
+    padding: 14Px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    .button {
+      width: 100Px;
+      font-size: 14Px;
+      cursor: pointer;
+      background-color: #006eff;
+      color: #ffffff;
+      padding: 8Px 20Px;
+      border-radius: 4Px;
+      border: none;
+      font-size: 14Px;
+      text-align: center;
+      line-height: 20Px;
+    }
+  }
+}
+</style>

+ 122 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/color.scss

@@ -0,0 +1,122 @@
+.manage {
+  background: #ffffff;
+  box-shadow: 0 1Px 10Px 0 rgba(2, 16, 43, 0.15);
+
+  &-header {
+    border-bottom: 1Px solid #e8e8e9;
+
+    h1 {
+      font-family: PingFangSC-Medium;
+      font-weight: 500;
+      color: #000000;
+    }
+
+    &-left {
+      main {
+        p {
+          font-weight: 400;
+          color: #999999;
+        }
+      }
+    }
+  }
+
+  .main {
+    .userInfo {
+      ol {
+        dl {
+          .userInfo-mask {
+            background: #ffffff;
+            box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+          }
+        }
+      }
+    }
+
+    .footer {
+      li {
+        font-weight: 400;
+        color: #dc2113;
+        border-bottom: 1Px solid #e8e8e9;
+      }
+    }
+  }
+
+  .admin {
+    &-content {
+      aside {
+        font-weight: 400;
+        color: #000000;
+        letter-spacing: 0;
+
+        p {
+          opacity: 0.6;
+        }
+      }
+    }
+
+    &-list {
+      label {
+        font-weight: 400;
+        color: #000000;
+      }
+    }
+
+    .last {
+      &::before {
+        background: #e8e8e9;
+      }
+    }
+  }
+
+  ol {
+    dl {
+      .userInfo-mask {
+        background: #ffffff;
+        box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+      }
+    }
+  }
+}
+
+.input {
+  border: 1Px solid #e8e8e9;
+  font-weight: 400;
+  color: #000000;
+  opacity: 0.6;
+}
+
+.avatar {
+  background: #f4f5f9;
+  color: #000000;
+}
+
+.space-top {
+  border-top: 10Px solid #f4f5f9;
+}
+
+.btn {
+  background: #3370ff;
+  border: 0 solid #2f80ed;
+  color: #ffffff;
+
+  &-cancel {
+    background: #ffffff;
+    border: 1Px solid #dddddd;
+    color: #828282;
+  }
+}
+
+.slider {
+  &-box {
+    background: #e1e1e3;
+  }
+
+  &-block {
+    background: #ffffff;
+    border: 0 solid rgba(0, 0, 0, 0.85);
+    box-shadow: 0 2Px 4Px 0 #d1d1d1;
+  }
+}
+
+.manage-h5 {}

+ 8 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/h5.scss

@@ -0,0 +1,8 @@
+
+.manage-h5 {
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+}

+ 5 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/index.scss

@@ -0,0 +1,5 @@
+@import './color.scss';
+@import './web.scss';
+@import './h5.scss';
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');

+ 329 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/manage-components/style/web.scss

@@ -0,0 +1,329 @@
+.manage {
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  width: 360Px;
+  overflow-y: auto;
+  border-radius: 8Px 0 0 8Px;
+  position: absolute;
+  right: 0;
+  height: 100%;
+  z-index: 2;
+  top: 0Px;
+
+  &-header {
+    padding: 12Px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    aside {
+      display: flex;
+      align-items: center;
+    }
+
+    h1 {
+      font-size: 16Px;
+    }
+
+    &-left {
+      display: flex;
+
+      .icon {
+        margin-right: 14Px;
+      }
+
+      main {
+        display: flex;
+        flex-direction: column;
+
+        p {
+          padding-top: 8Px;
+          font-size: 12Px;
+        }
+      }
+    }
+  }
+
+  .main {
+
+    .userInfo {
+      padding: 0 20Px;
+      display: flex;
+      flex-direction: column;
+      font-size: 14Px;
+
+      &-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 14Px 0;
+
+        p {
+          display: flex;
+          align-items: center;
+        }
+      }
+
+      ol {
+        flex: 1;
+        display: flex;
+        flex-wrap: wrap;
+        padding-bottom: 20Px;
+
+        dl {
+          position: relative;
+          flex: 0 0 36Px;
+          display: flex;
+          flex-direction: column;
+          padding-right: 20Px;
+
+          &:last-child {
+            padding-right: 0;
+          }
+
+          .more {
+            padding-top: 10Px;
+          }
+
+          dd {
+            text-align: center;
+            max-width: 36Px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+
+          .userInfo-mask {
+            position: absolute;
+            z-index: 5;
+            padding: 20Px;
+            left: 100%;
+
+            li {
+              display: flex;
+              align-items: center;
+
+              label {
+                width: 60Px;
+              }
+
+              span {
+                max-width: 200Px;
+                word-break: keep-all;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    .content {
+      padding: 0 20Px;
+
+      li {
+        padding: 14Px 0;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        font-size: 14Px;
+
+        .btn {
+          flex: 1;
+        }
+
+        article {
+          opacity: 0.6;
+          width: 246Px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .end {
+          align-self: flex-end;
+          margin-bottom: 4Px;
+        }
+      }
+    }
+
+    .footer {
+      padding: 0 20Px;
+
+      li {
+        cursor: pointer;
+        width: 100%;
+        font-size: 14Px;
+        padding: 14Px 0;
+        text-align: center;
+
+        &:last-child {
+          border: none;
+        }
+      }
+    }
+  }
+
+  .admin {
+    padding: 20Px 0;
+
+    &-content {
+      padding: 20Px 20Px 12Px;
+      display: flex;
+      align-items: center;
+
+      aside {
+        flex: 1;
+        font-size: 14Px;
+
+        p {
+          font-size: 12Px;
+        }
+      }
+    }
+
+    &-list {
+      padding: 0 20Px;
+
+      label {
+        display: inline-block;
+        font-size: 14Px;
+        padding-bottom: 8Px;
+      }
+    }
+
+    .last {
+      padding-top: 13Px;
+      position: relative;
+
+      &::before {
+        position: absolute;
+        content: '';
+        width: calc(100% - 40Px);
+        height: 1Px;
+        top: 0;
+        left: 0;
+        right: 0;
+        margin: 0 auto;
+      }
+    }
+  }
+
+  ol {
+    flex: 1;
+    display: flex;
+    flex-wrap: wrap;
+    padding-bottom: 20Px;
+
+    dl {
+      position: relative;
+      flex: 0 0 36Px;
+      display: flex;
+      flex-direction: column;
+      padding-right: 20Px;
+
+      &:last-child {
+        padding-right: 0;
+      }
+
+      .more {
+        padding-top: 10Px;
+      }
+
+      dd {
+        text-align: center;
+        max-width: 36Px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      .userInfo-mask {
+        position: fixed;
+        z-index: 5;
+        padding: 20Px;
+        margin-left: 36Px;
+
+        li {
+          display: flex;
+          align-items: center;
+
+          label {
+            width: 60Px;
+          }
+
+          span {
+            max-width: 200Px;
+            word-break: keep-all;
+          }
+        }
+      }
+    }
+  }
+}
+
+.input {
+  border-radius: 4Px;
+  padding: 4Px 16Px;
+  font-size: 14Px;
+}
+
+.groupID {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+
+  span {
+    padding-right: 10Px;
+  }
+
+  .icon {
+    width: 15Px;
+    height: 15Px;
+    cursor: pointer;
+  }
+}
+
+.avatar {
+  width: 36Px;
+  height: 36Px;
+  border-radius: 4Px;
+  font-size: 12Px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.btn {
+  padding: 4Px 28Px;
+  font-size: 12Px;
+  line-height: 24Px;
+  border-radius: 4Px;
+}
+
+.slider {
+  &-box {
+    display: flex;
+    align-items: center;
+    width: 34Px;
+    height: 20Px;
+    border-radius: 10Px;
+  }
+
+  &-block {
+    display: inline-block;
+    width: 16Px;
+    height: 16Px;
+    border-radius: 8Px;
+    margin: 0 2Px;
+  }
+}
+
+.space-between {
+  justify-content: space-between;
+}
+
+.delDialog-title {
+  text-align: center;
+  padding: 20Px 0;
+}

+ 2 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/index.ts

@@ -0,0 +1,2 @@
+import MessageInput from './index.vue';
+export default MessageInput;

+ 247 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/index.vue

@@ -0,0 +1,247 @@
+<template>
+  <div :class="['message-input', isH5 && 'message-input-h5']">
+    <MessageInputEditor
+      ref="editor"
+      :isH5="isH5"
+      :placeholder="placeholder"
+      :isGroup="isGroup"
+      :isMute="isMute"
+      :muteText="muteText"
+      :enableInput="enableInput"
+      :enableAt="enableAt"
+      :enableTyping="enableTyping"
+      :enableDragUpload="enableDragUpload"
+      @sendMessage="sendMessage"
+      @onTyping="onTyping"
+    ></MessageInputEditor>
+    <MessageInputButton :isH5="isH5" @sendMessage="sendMessage" v-if="!isMute"></MessageInputButton>
+    <MessageInputAt
+      :memberList="memberList"
+      :isGroup="isGroup"
+      :selfInfo="conversation?.groupProfile?.selfInfo"
+      :isH5="isH5"
+      v-if="enableAt"
+    ></MessageInputAt>
+    <MessageInputReferenceOrReply
+      :replyOrReference="replyOrReference"
+      :isH5="isH5"
+      @resetReplyOrReference="resetReplyOrReference"
+    ></MessageInputReferenceOrReply>
+  </div>
+</template>
+<script setup lang="ts">
+import { defineProps, defineEmits, toRefs, ref, defineExpose, watch } from 'vue';
+import MessageInputEditor from './message-input-editor.vue';
+import MessageInputAt from './message-input-at.vue';
+import MessageInputButton from './message-input-button.vue';
+import MessageInputReferenceOrReply from './message-input-reference-or-reply.vue';
+import { JSONToObject } from '../utils/utils';
+import { handleErrorPrompts } from '../../utils';
+
+const props = defineProps({
+  placeholder: {
+    type: String,
+    default: 'this is placeholder',
+  },
+  conversation: {
+    type: Object,
+    default: () => ({}),
+  },
+  replyOrReference: {
+    type: Object,
+    default: () => ({}),
+  },
+  isGroup: {
+    type: Boolean,
+    default: false,
+  },
+  memberList: {
+    type: Array,
+    default: () => [],
+  },
+  isMute: {
+    type: Boolean,
+    default: true,
+  },
+  muteText: {
+    type: String,
+    default: '',
+  },
+  enableInput: {
+    type: Boolean,
+    default: true,
+  },
+  enableAt: {
+    type: Boolean,
+    default: true,
+  },
+  enableDragUpload: {
+    type: Boolean,
+    default: true,
+  },
+  enableTyping: {
+    type: Boolean,
+    default: true,
+  },
+  env: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const emit = defineEmits(['sendMessage', 'resetReplyOrReference', 'onTyping']);
+const { placeholder, isGroup, memberList, conversation, replyOrReference, env, enableTyping } = toRefs(props);
+const editor = ref();
+const isH5 = ref(props?.env?.isH5);
+
+watch(
+  () => conversation.value,
+  (newVal: any, oldVal: any) => {
+    if (newVal?.conversationID !== oldVal?.conversationID) {
+      // conversation change
+      editor?.value?.resetEditor();
+    }
+  },
+  {
+    immediate: true,
+  }
+);
+
+const sendMessage = async () => {
+  const TUIServer = (window as any)?.TUIKitTUICore?.TUIServer?.TUIChat;
+  const messageList = editor?.value?.getEditorContent();
+  const replyOrReferenceObject = replyOrReference.value;
+  await messageList?.forEach(async (content: any) => {
+    try {
+      let cloudCustomData, res;
+      // handle message typing
+      cloudCustomData = handleMessageWithTyping(cloudCustomData);
+      switch (content?.type) {
+        case 'text':
+          // 引用和回复只支持文本消息(对标微信)
+          // replay or reference message
+          cloudCustomData = handleMessageReplyOrReference(cloudCustomData);
+          // @ text message
+          if (content?.payload?.atUserList) {
+            res = await TUIServer?.sendTextAtMessage(
+              {
+                text: JSON.parse(JSON.stringify(content?.payload?.text)),
+                atUserList: content?.payload?.atUserList,
+              },
+              cloudCustomData
+            );
+          } else {
+            res = await TUIServer?.sendTextMessage(JSON.parse(JSON.stringify(content?.payload?.text)), cloudCustomData);
+          }
+          if (replyOrReferenceObject?.show === 'reply') {
+            await TUIServer.replyMessage(res?.data?.message);
+          }
+          break;
+        case 'image':
+          await TUIServer?.sendImageMessage(content?.payload?.file);
+          break;
+        case 'video':
+          await TUIServer?.sendVideoMessage(content?.payload?.file);
+          break;
+        case 'file':
+          await TUIServer?.sendFileMessage(content?.payload?.file);
+          break;
+        default:
+          break;
+      }
+      emit('sendMessage');
+    } catch (error: any) {
+      handleErrorPrompts(error, env.value);
+    }
+  });
+  editor?.value?.resetEditor();
+  resetReplyOrReference();
+};
+
+const onTyping = (inputContentEmpty: boolean, inputBlur: boolean) => {
+  emit('onTyping', inputContentEmpty, inputBlur);
+};
+
+const handleMessageWithTyping = (cloudCustomData: any) => {
+  if (enableTyping.value) {
+    if (!cloudCustomData) {
+      cloudCustomData = {};
+    }
+    cloudCustomData.messageFeature = {
+      needTyping: 1,
+      version: 1,
+    };
+  }
+  return cloudCustomData;
+};
+
+const handleMessageReplyOrReference = (cloudCustomData: any) => {
+  if (replyOrReference?.value?.show !== 'reply' && replyOrReference?.value?.show !== 'reference') {
+    return cloudCustomData;
+  }
+  if (!cloudCustomData) {
+    cloudCustomData = {};
+  }
+  cloudCustomData.messageReply = {
+    messageAbstract: replyOrReference?.value?.content,
+    messageSender: replyOrReference?.value?.message?.nick || replyOrReference?.value?.message?.from,
+    messageID: replyOrReference?.value?.message?.ID,
+    messageType: replyOrReference?.value?.type,
+    version: 1,
+  };
+  if (replyOrReference?.value?.show === 'reply') {
+    try {
+      cloudCustomData.messageReply.messageRootID = replyOrReference?.value?.message?.ID;
+      if (replyOrReference?.value?.message?.cloudCustomData) {
+        const replyMessageCloudCustomData = JSONToObject(replyOrReference?.value?.message?.cloudCustomData as any);
+        cloudCustomData.messageReply.messageRootID =
+          replyMessageCloudCustomData?.messageReply?.messageRootID || replyOrReference?.value?.message?.ID;
+      }
+    } catch (error) {
+      console.warn(error);
+    }
+  }
+  return cloudCustomData;
+};
+
+const addEmoji = (emoji: any) => {
+  editor?.value?.addEmoji(emoji);
+};
+
+const resetReplyOrReference = () => {
+  emit('resetReplyOrReference');
+};
+
+const reEdit = (content: any) => {
+  editor?.value?.resetEditor();
+  resetReplyOrReference();
+  editor?.value?.setEditorContent(content);
+};
+
+defineExpose({
+  addEmoji,
+  reEdit,
+});
+</script>
+
+<style scoped lang="scss">
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-input {
+  flex: 1;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  border: none;
+  height: 100%;
+  width: 100%;
+  max-height: 100%;
+  max-width: 100%;
+  overflow: hidden;
+}
+.message-input-h5 {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+}
+</style>

+ 392 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-at.vue

@@ -0,0 +1,392 @@
+<template>
+  <div
+    class="message-input-at"
+    :class="[isH5 && 'message-input-at-h5']"
+    v-if="showAtList"
+    ref="MessageInputAt"
+  >
+    <div class="memberList" ref="dialog">
+      <header class="memberList-title" v-if="isH5">
+        <span class="title">{{ $t('TUIChat.选择提醒的人') }}</span>
+        <i class="icon icon-close close" @click="closeAt"></i>
+      </header>
+      <ul class="memberList-box">
+        <li
+          class="memberList-box-body"
+          :class="[index === selectedIndex && 'selected']"
+          v-for="(item, index) in showMemberList"
+          :key="index"
+          @click="selectItem(index)"
+          ref="memberListItems"
+        >
+          <img
+            :src="(item as any)?.avatar || 'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+          />
+          <span>
+            {{
+              (item as any)?.nick ? (item as any)?.nick : (item as any)?.userID
+            }}
+          </span>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { defineComponent, ref, toRefs, watchEffect, watch } from 'vue';
+import { SuggestionProps, SuggestionKeyDownProps } from '@tiptap/suggestion';
+import atIcon from '../../../assets/icon/at.svg';
+import TIM from '../../../../TUICore/tim';
+import { onClickOutside } from '@vueuse/core';
+
+const MessageInputAt = ref();
+const showAtList = ref(false);
+const allMemberList = ref<Array<any>>();
+const showMemberList = ref<Array<any>>();
+const position = ref({
+  left: 0,
+  top: 0
+});
+const command = ref();
+const selectedIndex = ref(0);
+const memberListItems = ref();
+const isH5 = ref(false);
+
+const MessageInputAtSuggestion = () => {
+  return {
+    allowedPrefixes: null,
+    items: (props: { query: string }) => {
+      const queryResult = allMemberList?.value?.filter(
+        item =>
+          item?.nick?.toLowerCase()?.startsWith(props?.query?.toLowerCase()) ||
+          item?.userID?.toLowerCase()?.startsWith(props?.query?.toLowerCase())
+      );
+      showMemberList.value = queryResult?.length
+        ? queryResult
+        : allMemberList.value;
+      return showMemberList.value;
+    },
+    render: () => {
+      return {
+        onStart: (
+          props: SuggestionProps<{
+            id?: string;
+            userID?: string;
+            isAll?: boolean;
+          }>
+        ) => {
+          showAtList.value = true;
+          if (!props?.clientRect) {
+            return;
+          }
+          const rect = props?.clientRect();
+          if (rect?.left && rect?.top && !isH5.value) {
+            position.value = {
+              left: rect?.left,
+              top: rect?.top
+            };
+          }
+          command.value = props.command;
+        },
+
+        onUpdate(props: SuggestionProps<any>) {
+          if (!props?.clientRect) {
+            return;
+          }
+          const rect = props?.clientRect();
+          if (rect?.left && rect?.top && !isH5.value) {
+            position.value = {
+              left: rect?.left,
+              top: rect?.top
+            };
+          }
+        },
+
+        onKeyDown(props: SuggestionKeyDownProps) {
+          if (props.event.key === 'Enter') {
+            props.event?.stopPropagation();
+            props.event?.preventDefault();
+          }
+          if (props.event.key === 'Escape') {
+            showAtList.value = false;
+            showMemberList.value = allMemberList.value;
+            return true;
+          }
+          if (props?.event.key === 'ArrowUp') {
+            upHandler();
+            return true;
+          }
+          if (props?.event.key === 'ArrowDown') {
+            downHandler();
+            return true;
+          }
+          if (props?.event.key === 'Enter') {
+            enterHandler();
+            return true;
+          }
+          return false;
+        },
+
+        onExit(props: SuggestionProps<any>) {
+          showAtList.value = false;
+          showMemberList.value = allMemberList.value;
+          position.value = {
+            left: 0,
+            top: 0
+          };
+        }
+      };
+    }
+  };
+};
+
+const upHandler = () => {
+  if (!showMemberList?.value?.length) return;
+  selectedIndex.value =
+    (selectedIndex.value + showMemberList?.value?.length - 1) %
+    showMemberList?.value?.length;
+  memberListItems?.value[selectedIndex.value]?.scrollIntoView(false);
+};
+
+const downHandler = () => {
+  if (!showMemberList?.value?.length) return;
+  selectedIndex.value =
+    (selectedIndex.value + 1) % showMemberList?.value?.length;
+  memberListItems?.value[selectedIndex.value]?.scrollIntoView(false);
+};
+
+const enterHandler = () => {
+  selectItem(selectedIndex.value);
+};
+
+const selectItem = (index: number) => {
+  if (!showMemberList?.value?.length) return;
+  const item = showMemberList?.value[index];
+  if (item) {
+    command.value &&
+      command.value({
+        id: (item as any)?.userID,
+        label: (item as any)?.nick || (item as any)?.userID
+      });
+  }
+};
+
+const MessageInputAtComponent = defineComponent({
+  props: {
+    memberList: {
+      type: Array,
+      default: () => []
+    },
+    isGroup: {
+      type: Boolean,
+      default: false
+    },
+    selfInfo: {
+      type: Object,
+      default: () => ({})
+    },
+    isH5: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props) {
+    const { memberList, isGroup, selfInfo } = toRefs(props);
+    const all = {
+      userID: TIM.TYPES.MSG_AT_ALL,
+      nick: '所有人',
+      isAll: true,
+      avatar: atIcon
+    };
+    const dialog = ref();
+    watchEffect(() => {
+      showAtList.value = showAtList.value && isGroup.value;
+      isH5.value = props.isH5;
+    });
+
+    watch(
+      () => memberList.value,
+      () => {
+        // add all
+        if (!(memberList?.value[0] as any)?.isAll) {
+          memberList?.value?.unshift(all);
+        }
+        // delete self in @ list
+        const list = memberList?.value?.filter((item: any) => {
+          return item?.userID !== selfInfo?.value?.userID;
+        });
+        allMemberList.value = list;
+        showMemberList.value = list;
+      },
+      {
+        deep: true,
+        immediate: true
+      }
+    );
+
+    watch(
+      () => [position.value, MessageInputAt?.value],
+      () => {
+        if (
+          isH5.value ||
+          !MessageInputAt?.value ||
+          !MessageInputAt?.value?.style
+        ) {
+          return;
+        }
+        MessageInputAt.value.style.left = position.value.left + 'Px';
+        MessageInputAt.value.style.top =
+          position.value.top - MessageInputAt.value.clientHeight + 'Px';
+      },
+      {
+        deep: true,
+        immediate: true
+      }
+    );
+
+    const closeAt = () => {
+      showAtList.value = false;
+      showMemberList.value = allMemberList.value;
+      position.value = {
+        left: 0,
+        top: 0
+      };
+    };
+
+    onClickOutside(dialog, () => {
+      closeAt();
+    });
+
+    return {
+      selectedIndex,
+      selectItem,
+      showAtList,
+      closeAt,
+      showMemberList,
+      allMemberList,
+      MessageInputAt,
+      memberListItems,
+      dialog
+    };
+  }
+});
+
+export default MessageInputAtComponent;
+export { MessageInputAtSuggestion, MessageInputAtComponent };
+</script>
+<style scoped lang="scss">
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-input-at {
+  position: fixed;
+  max-width: 15rem;
+  max-height: 10rem;
+  overflow-x: hidden;
+  overflow-y: auto;
+  background: #ffffff;
+  box-shadow: 0 0.06rem 0.63rem 0 rgba(2, 16, 43, 0.15);
+  border-radius: 0.13rem;
+}
+.memberList-box {
+  &-header {
+    height: 2.5rem;
+    padding-top: 5Px;
+    cursor: pointer;
+
+    &:hover {
+      background: rgba(0, 110, 255, 0.1);
+    }
+  }
+  span {
+    font-family: PingFangSC-Regular;
+    font-weight: 400;
+    font-size: 0.88rem;
+    color: #000000;
+    letter-spacing: 0;
+    padding: 5Px;
+  }
+  &-body {
+    height: 2.5rem;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    .selected,
+    &:hover {
+      background: rgba(0, 110, 255, 0.1);
+    }
+    span {
+      overflow: hidden;
+      white-space: nowrap;
+      word-wrap: break-word;
+      word-break: break-all;
+      text-overflow: ellipsis;
+    }
+  }
+  img {
+    width: 1.5rem;
+    height: 1.5rem;
+    padding-left: 10Px;
+  }
+  .selected {
+    background: rgba(0, 110, 255, 0.1);
+  }
+}
+
+.message-input-at-h5 {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  max-width: 100%;
+  max-height: 100%;
+  overflow: hidden;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 10;
+  display: flex;
+  align-items: flex-end;
+  .memberList {
+    height: auto;
+    max-height: 50%;
+    width: 100%;
+    max-width: 100%;
+    background: white;
+    border-radius: 12Px 12Px 0 0;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    &-title {
+      height: fit-content;
+      width: calc(100% - 30Px);
+      text-align: center;
+      vertical-align: middle;
+      padding: 15Px;
+      .title {
+        vertical-align: middle;
+        display: inline-block;
+        font-size: 16Px;
+      }
+      .close {
+        vertical-align: middle;
+        position: absolute;
+        right: 10Px;
+        display: inline-block;
+      }
+    }
+    &-box {
+      flex: 1;
+      overflow-y: scroll;
+      &-body {
+        padding: 10Px;
+        img {
+          width: 26Px;
+          height: 26Px;
+        }
+        span {
+          font-size: 14Px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 96 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-button.vue

@@ -0,0 +1,96 @@
+<template>
+  <div :class="['message-input-button', isH5 && 'message-input-button-h5']">
+    <button
+      v-if="enableSend"
+      class="message-input-button-cont"
+      data-type="text"
+      @click="sendMessage"
+      :disabled="false"
+    >
+      <p class="message-input-button-hover">
+        {{ $t('TUIChat.按Enter发送,Ctrl+Enter换行') }}
+      </p>
+      {{ $t('发送') }}
+    </button>
+  </div>
+</template>
+<script setup lang="ts">
+import { defineProps, toRefs, defineEmits, ref } from 'vue';
+const props = defineProps({
+  enableSend: {
+    type: Boolean,
+    default: true
+  },
+  messages: {
+    type: Array,
+    default: () => []
+  },
+  isH5: {
+    type: Boolean,
+    default: true
+  }
+});
+const { enableSend } = toRefs(props);
+const emits = defineEmits(['sendMessage']);
+const sendMessage = () => {
+  emits('sendMessage');
+};
+</script>
+<style scoped lang="scss">
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-input-button {
+  position: absolute;
+  bottom: 20Px;
+  right: 20Px;
+  &-h5 {
+    position: static;
+  }
+  &-cont {
+    padding: 8Px 20Px;
+    border-radius: 4Px;
+    border: none;
+    font-size: 14Px;
+    text-align: center;
+    line-height: 20Px;
+    font-weight: 400;
+    background: #006eff;
+    color: #ffffff;
+    letter-spacing: 0;
+    cursor: pointer;
+  }
+  &:hover {
+    .message-input-button-hover {
+      display: flex;
+    }
+  }
+  &-hover {
+    display: none;
+    justify-content: center;
+    align-items: center;
+    position: absolute;
+    right: 120%;
+    word-break: keep-all;
+    height: 30Px;
+    white-space: nowrap;
+    top: 0;
+    bottom: 0;
+    margin: auto 0;
+    padding: 5Px 10Px;
+    border-radius: 3Px;
+    background: #000000;
+    color: #ffffff;
+    opacity: 0.3;
+
+    &::before {
+      content: '';
+      position: absolute;
+      width: 0;
+      height: 0;
+      right: -20Px;
+      border: 10Px solid transparent;
+      border-left: 10Px solid #000000;
+    }
+  }
+}
+</style>

+ 565 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-editor.vue

@@ -0,0 +1,565 @@
+<template>
+  <div
+    :class="['message-input-container', isH5 && 'message-input-container-h5']"
+  >
+    <div class="message-input-mute" v-show="isMute">
+      {{ $t(`TUIChat.${muteText}`) }}
+    </div>
+    <editor-content
+      v-show="!isMute && enableInput"
+      :editor="editor"
+      class="message-input-area"
+      ref="editorContainer"
+      @drop="(e:any) => handleFileDropOrPaste(e, 'drop')"
+      @paste="(e:any) => handleFileDropOrPaste(e, 'paste')"
+      @keydown.enter="handleEnter"
+    />
+  </div>
+</template>
+<script setup lang="ts">
+import { defineProps, defineEmits, toRefs, ref, defineExpose } from 'vue';
+import { useEditor, EditorContent } from '@tiptap/vue-3';
+import Document from '@tiptap/extension-document';
+import Paragraph from '@tiptap/extension-paragraph';
+import Placeholder from '@tiptap/extension-placeholder';
+import Text from '@tiptap/extension-text';
+import Mention from '@tiptap/extension-mention';
+import CustomImage from './message-input-file';
+import { MessageInputAtSuggestion } from './message-input-at.vue';
+
+const props = defineProps({
+  placeholder: {
+    type: String,
+    default: 'this is placeholder'
+  },
+  replayOrReferenceMessage: {
+    type: Object,
+    default: () => ({})
+  },
+  isMute: {
+    type: Boolean,
+    default: true
+  },
+  muteText: {
+    type: String,
+    default: ''
+  },
+  enableInput: {
+    type: Boolean,
+    default: true
+  },
+  enableAt: {
+    type: Boolean,
+    default: true
+  },
+  enableDragUpload: {
+    type: Boolean,
+    default: true
+  },
+  enableTyping: {
+    type: Boolean,
+    default: true
+  },
+  isH5: {
+    type: Boolean,
+    default: true
+  },
+  isGroup: {
+    type: Boolean,
+    default: false
+  }
+});
+const emits = defineEmits(['sendMessage', 'onTyping']);
+const { placeholder, isH5, enableAt, enableDragUpload, isGroup, enableTyping } =
+  toRefs(props);
+const inputContentEmpty = ref(true);
+const inputBlur = ref(true);
+
+const editor = useEditor({
+  extensions: [
+    Document,
+    Paragraph,
+    Text,
+    Placeholder.configure({
+      emptyEditorClass: 'is-editor-empty',
+      placeholder: placeholder.value
+    }),
+    Mention.configure({
+      HTMLAttributes: {
+        class: 'mention'
+      },
+      suggestion: enableAt.value && (MessageInputAtSuggestion() as any)
+    }),
+    CustomImage.configure({
+      inline: true,
+      allowBase64: true,
+      HTMLAttributes: {
+        class: 'custom-image'
+      }
+    })
+  ],
+  autofocus: true,
+  editable: true,
+  injectCSS: false,
+
+  // handle input edtor typing (only in C2C and enable typing)
+  onUpdate({ editor, transaction }) {
+    if (!enableTyping.value || isGroup.value) return;
+    inputBlur.value = !editor.isFocused;
+    if (transaction?.doc?.content?.size > 2) {
+      inputContentEmpty.value = false;
+    } else {
+      inputContentEmpty.value = true;
+    }
+    emits('onTyping', inputContentEmpty.value, inputBlur.value);
+  },
+  onFocus() {
+    if (isH5.value && document?.getElementById('app')?.style) {
+      // set app height when keyboard popup
+      const keyboardHeight = document.body.scrollHeight - window.innerHeight;
+      (
+        document.getElementById('app') as any
+      ).style.marginBottom = `${keyboardHeight}Px`;
+      (
+        document.getElementById('app') as any
+      ).style.height = `calc(100% - ${keyboardHeight}Px)`;
+    }
+    if (!enableTyping.value || isGroup.value) return;
+    inputBlur.value = true;
+    emits('onTyping', inputContentEmpty.value, inputBlur.value);
+  },
+  onBlur() {
+    if (isH5.value && document?.getElementById('app')?.style) {
+      // reset app height to normal
+      (document.getElementById('app') as any).style.marginBottom = ``;
+      (document.getElementById('app') as any).style.height = `100%`;
+    }
+    if (!enableTyping.value || isGroup.value) return;
+    inputBlur.value = true;
+    emits('onTyping', inputContentEmpty.value, inputBlur.value);
+  }
+});
+
+const editorContainer = ref();
+
+const handleEnter = (e: any) => {
+  if (isH5?.value) {
+    return;
+  }
+  e?.preventDefault();
+  e?.stopPropagation();
+  if (e.keyCode === 13 && e.ctrlKey) {
+    // ctrl + enter: warp
+    editor?.value?.commands?.insertContent('<p></p>');
+  } else if (e.keyCode === 13) {
+    // enter only: send message
+    emits('sendMessage');
+  }
+};
+
+// fileMap 存储 fileURL 与 fileObject 的映射
+const fileMap = new Map<string, any>();
+const handleFileDropOrPaste = async (e: any, type: string) => {
+  e.preventDefault();
+  e.stopPropagation();
+  if (isH5.value) {
+    return;
+  }
+  if (!enableDragUpload?.value && type === 'drop') {
+    return;
+  }
+  if (
+    (type === 'drop' && e.dataTransfer) ||
+    (type === 'paste' && e.clipboardData)
+  ) {
+    const files =
+      type === 'drop' ? e?.dataTransfer?.files : e?.clipboardData?.files;
+    for (let i = 0; i < files.length; i++) {
+      const file = files[i];
+      const isImage = file.type.startsWith('image/');
+      const fileSrc = isImage
+        ? URL.createObjectURL(file)
+        : await drawFileCanvasToImageUrl(file);
+      editor?.value?.commands?.insertContent({
+        type: 'custom-image',
+        attrs: {
+          src: fileSrc,
+          alt: file?.name,
+          title: file?.name,
+          class: isImage ? 'normal' : 'file'
+        }
+      });
+      fileMap.set(fileSrc, file);
+      if (i === files.length - 1) {
+        setTimeout(() => {
+          editor?.value?.commands?.focus('end');
+          editor?.value?.commands?.scrollIntoView();
+        }, 10);
+      }
+    }
+  }
+};
+
+// create file icon image
+// 为了避免重复创建拥有相同icon图标的img dom,将之前已有类型进行记录
+// 记录格式为 map<icon type,img dom>
+const fileIconDomMap = new Map<string, HTMLImageElement>();
+const addImageProcess = (src: string, type: string) => {
+  return new Promise((resolve, reject) => {
+    if (fileIconDomMap.has(type)) {
+      resolve(fileIconDomMap.get(type));
+    } else {
+      let img = new Image();
+      img.crossOrigin = 'anonymous';
+      img.onload = () => {
+        fileIconDomMap.set(type, img);
+        resolve(img);
+      };
+      img.onerror = reject;
+      img.src = src;
+    }
+  });
+};
+
+// draw file tag canvas
+const drawFileCanvasToImageUrl = async (file: any) => {
+  const { name, type } = file;
+  const canvas = document.createElement('canvas');
+  let width = 160;
+  let height = 50;
+  canvas.style.width = width + 'Px';
+  canvas.style.height = height + 'Px';
+  // 设置内存中的实际大小(缩放以考虑额外的像素密度)
+  let scale = window.devicePixelRatio; // 在视网膜屏幕上更改为 1 以查看模糊
+  canvas.width = Math.floor(width * scale);
+  canvas.height = Math.floor(height * scale);
+  const ctx = canvas.getContext('2d');
+  if (!ctx) {
+    return '';
+  }
+  // 标准化坐标系以使用 css 像素
+  ctx.scale(scale, scale);
+  // draw icon
+  const { iconSrc, iconType } = handleFileIconForShow(type);
+  const img = await addImageProcess(iconSrc, iconType);
+  ctx?.drawImage(img as any, 10, 10, 30, 30);
+  // draw font
+  const nameForShow = handleNameForShow(name);
+  ctx.fillText(nameForShow, 45, 22);
+  // canvas to url
+  const dataURL = canvas.toDataURL();
+  return dataURL;
+};
+
+const handleFileIconForShow = (type: string) => {
+  const urlBase = 'https://web.sdk.qcloud.com/component/TUIKit/assets/file-';
+  const fileTypes = [
+    'image',
+    'pdf',
+    'text',
+    'ppt',
+    'presentation',
+    'sheet',
+    'zip',
+    'word',
+    'video',
+    'unknown'
+  ];
+  let url = '';
+  let iconType = '';
+  fileTypes.forEach((typeName: string) => {
+    if (type.includes(typeName)) {
+      url = urlBase + typeName + '.svg';
+      iconType = typeName;
+    }
+  });
+  return {
+    iconSrc: url ? url : urlBase + 'unknown.svg',
+    iconType: iconType ? iconType : 'unknown'
+  };
+};
+
+// 获取字符串的实际占位长度(字母or符号:1,其他(主要是中文):)
+const handleNameForShow = (value: string): string => {
+  if (!value) {
+    return value;
+  }
+  let res = '';
+  let length = 0;
+  for (let i = 0; i < value?.length; i++) {
+    if (length > 16) {
+      res += '...';
+      break;
+    }
+    res += value[i];
+    if (/[a-z]|[0-9]|[,;.!@#-+/\\$%^*()<>?:"'{}~]/i.test(value[i])) {
+      length += 1;
+    } else {
+      length += 2;
+    }
+  }
+  return res;
+};
+
+const getEditorContent = () => {
+  return handleEditorForMessage();
+};
+
+const handleEditorForMessage = () => {
+  const editorJSON = editor?.value?.getJSON();
+  const content: any[] = [];
+  const handleEditorContent = (root: any) => {
+    if (!root || !root.type) {
+      return;
+    } else if (
+      root.type !== 'text' &&
+      root.type !== 'custom-image' &&
+      root.type !== 'mention'
+    ) {
+      if (root.type === 'paragraph') {
+        handleEditorNode(root);
+      }
+      if (root.content && root.content.length) {
+        root.content.forEach((item: any) => {
+          handleEditorContent(item);
+        });
+      }
+      return;
+    } else {
+      handleEditorNode(root);
+    }
+  };
+  const handleEditorNode = (node: any) => {
+    // handle enter
+    if (node.type === 'paragraph') {
+      if (
+        content.length > 0 &&
+        content[content.length - 1] &&
+        content[content.length - 1]?.type === 'text'
+      ) {
+        content[content.length - 1].payload.text += '\n';
+      }
+    } else if (
+      node.type === 'text' ||
+      (node.type === 'custom-image' && node?.attrs?.class === 'emoji')
+    ) {
+      const text = node.type === 'text' ? node?.text : node?.attrs?.alt;
+      if (
+        content.length > 0 &&
+        content[content.length - 1] &&
+        content[content.length - 1]?.type === 'text'
+      ) {
+        content[content.length - 1].payload.text += text;
+      } else {
+        content.push({
+          type: 'text',
+          payload: { text: text }
+        });
+      }
+    } else if (
+      node.type === 'custom-image' &&
+      node?.attrs?.class === 'normal'
+    ) {
+      content.push({
+        type: 'image',
+        payload: { file: fileMap?.get(node?.attrs?.src) }
+      });
+    } else if (node.type === 'custom-image' && node?.attrs?.class === 'file') {
+      const file = fileMap?.get(node?.attrs?.src);
+      content.push({
+        type: file?.type?.includes('video') ? 'video' : 'file',
+        payload: { file }
+      });
+    } else if (node.type === 'mention') {
+      const text = '@' + node?.attrs?.label + ' ';
+      if (
+        content.length > 0 &&
+        content[content.length - 1] &&
+        content[content.length - 1]?.type === 'text'
+      ) {
+        content[content.length - 1].payload.text += text;
+      } else {
+        content.push({
+          type: 'text',
+          payload: { text: text }
+        });
+      }
+      if (content[content.length - 1]?.payload?.atUserList) {
+        content[content.length - 1]?.payload?.atUserList?.push(node?.attrs?.id);
+      } else {
+        content[content.length - 1].payload.atUserList = [node?.attrs?.id];
+      }
+    }
+  };
+  handleEditorContent(editorJSON);
+  if (
+    content.length > 0 &&
+    content[content.length - 1] &&
+    content[content.length - 1]?.type === 'text' &&
+    content[content.length - 1]?.payload?.text?.endsWith('\n')
+  ) {
+    const text = content[content.length - 1].payload.text;
+    content[content.length - 1].payload.text = text?.substring(
+      0,
+      text.lastIndexOf('\n')
+    );
+  }
+  return content;
+};
+
+const addEmoji = (emoji: any) => {
+  editor?.value?.commands?.insertContent({
+    type: 'custom-image',
+    attrs: {
+      src: emoji?.url,
+      alt: emoji?.name,
+      title: emoji?.name,
+      class: 'emoji'
+    }
+  });
+  editor?.value?.commands.focus('end');
+  editor?.value?.commands?.scrollIntoView();
+};
+
+const resetEditor = () => {
+  editor?.value?.commands?.clearContent(true);
+  fileMap?.clear();
+  editor?.value?.commands?.focus('end');
+  inputBlur.value = true;
+  inputContentEmpty.value = true;
+};
+
+const setEditorContent = (content: any) => {
+  editor?.value?.commands?.insertContent(content);
+};
+
+defineExpose({
+  getEditorContent,
+  addEmoji,
+  resetEditor,
+  setEditorContent
+});
+</script>
+
+<style scoped lang="scss">
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-input {
+  &-container {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    height: calc(100% - 13Px);
+    width: calc(100% - 20Px);
+    padding: 3Px 10Px 10Px 10Px;
+    overflow: hidden;
+  }
+  &-area {
+    flex: 1;
+    display: flex;
+    overflow-y: scroll;
+
+    &::-webkit-scrollbar {
+      background: transparent;
+    }
+  }
+  &-mute {
+    flex: 1;
+    display: flex;
+    color: #999999;
+    font-size: 14Px;
+    justify-content: center;
+    align-items: center;
+  }
+}
+.message-input-container-h5 {
+  flex: 1;
+  height: auto;
+  background: #f4f5f9;
+  border-radius: 9.4Px;
+  padding: 7Px 0Px 7Px 10Px;
+  font-size: 16Px !important;
+  max-height: 86Px;
+  margin-right: 7Px;
+}
+</style>
+<style lang="scss">
+.ProseMirror {
+  min-height: 100%;
+  height: fit-content;
+  flex: 1;
+  font-size: 14Px;
+  word-wrap: break-word;
+  word-break: break-all;
+  white-space: pre-wrap;
+  div,
+  ul,
+  ol,
+  dl,
+  dt,
+  dd,
+  li,
+  dl,
+  h1,
+  h2,
+  h3,
+  h4,
+  p {
+    margin: 0;
+    padding: 0;
+    font-style: normal;
+  }
+  p {
+    * {
+      vertical-align: bottom;
+    }
+  }
+  -webkit-user-select: text;
+  user-select: text;
+  &-focused {
+    border: none;
+    outline: none;
+  }
+  img {
+    &.ProseMirror-selectednode {
+      outline: 2Px solid #68cef8;
+    }
+  }
+
+  .custom-image {
+    &-normal {
+      max-height: 120Px;
+      max-width: 200Px;
+    }
+    &-file {
+      height: 50Px;
+      width: 160Px;
+      border: 1Px solid #e8e8e9;
+      border-radius: 5Px;
+    }
+    &-emoji {
+      height: 20Px;
+      width: 20Px;
+    }
+  }
+
+  .ProseMirror-selectednode {
+    outline: 2Px solid #68cef8;
+    cursor: none;
+  }
+  p,
+  [contenteditable] {
+    -webkit-user-select: text;
+    user-select: text;
+  }
+
+  // placeholder style
+  p.is-editor-empty:first-child::before {
+    color: #adb5bd;
+    content: attr(data-placeholder);
+    float: left;
+    height: 0;
+    pointer-events: none;
+  }
+}
+</style>

+ 43 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-file.ts

@@ -0,0 +1,43 @@
+import Image from '@tiptap/extension-image'
+import { mergeAttributes } from '@tiptap/core'
+
+export default Image.extend({
+  name: 'custom-image',
+
+  addAttributes() {
+    return {
+      ...(Image.config as any).addAttributes(),
+      class: {
+        default: 'image',
+        rendered: false,
+      },
+    }
+  },
+
+  addCommands() {
+    return {
+      setImage: (options) => ({ tr, commands }) => {
+        if ((tr.selection as any)?.node?.type?.name == 'custom-image') {
+          return commands.updateAttributes('custom-image', options)
+        }
+        else {
+          return commands.insertContent({
+            type: this.name,
+            attrs: options
+          })
+        }
+      },
+    }
+  },
+
+  renderHTML({ node, HTMLAttributes }) {
+    HTMLAttributes.class = 'custom-image-' + node.attrs.class;
+
+    return [
+      'img',
+      mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)
+    ]
+  }
+})
+
+

+ 221 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/message-input/message-input-reference-or-reply.vue

@@ -0,0 +1,221 @@
+<template>
+  <div
+    :class="[
+      replyOrReference?.show === 'reference' && 'message-input-reference',
+      replyOrReference?.show === 'reply' && 'message-input-reply',
+      isH5 &&
+        replyOrReference?.show === 'reference' &&
+        'message-input-reference-h5',
+      isH5 && replyOrReference?.show === 'reply' && 'message-input-reply-h5'
+    ]"
+    v-if="replyOrReference?.show"
+  >
+    <div class="reference" v-if="replyOrReference?.show === 'reference'">
+      <div class="reference-box">
+        <div class="reference-box-show">
+          <span class="reference-box-show-name">
+            {{
+              replyOrReference?.message?.nick
+                ? replyOrReference?.message?.nick
+                : replyOrReference?.message?.from
+            }}:
+          </span>
+          <span>{{ replyOrReference?.content }}</span>
+        </div>
+        <label class="icon icon-cancel" @click="close"></label>
+      </div>
+    </div>
+    <div class="reply" v-else-if="replyOrReference?.show === 'reply'">
+      <div class="reply-box">
+        <i></i>
+        <div class="reply-box-show">
+          <span>
+            {{
+              replyOrReference?.message?.nick
+                ? replyOrReference?.message?.nick
+                : replyOrReference?.message?.from
+            }}{{ isH5 ? ':' : '' }}
+          </span>
+          <span>{{ replyOrReference?.content }}</span>
+        </div>
+        <label class="icon icon-cancel" @click="close"></label>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { defineProps, toRefs, defineEmits } from 'vue';
+const props = defineProps({
+  replyOrReference: {
+    type: Object,
+    default: () => ({})
+  },
+  isH5: {
+    type: Boolean,
+    default: false
+  }
+});
+const emit = defineEmits(['resetReplyOrReference']);
+const { replyOrReference, isH5 } = toRefs(props);
+const close = () => {
+  emit('resetReplyOrReference');
+};
+</script>
+<style lang="scss" scoped>
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+.message-input-reference {
+  order: 1;
+  .reference {
+    width: auto;
+    padding-bottom: 0Px;
+    margin: 0Px 100Px 10Px 10Px;
+    display: flex;
+
+    &-box {
+      padding: 0;
+      overflow: hidden;
+      width: max-content;
+      padding: 10Px;
+      background-color: #fbfbfb;
+      display: flex;
+      border-radius: 8Px;
+
+      label {
+        cursor: pointer;
+        margin-top: 5Px;
+      }
+
+      &-show {
+        width: max-content;
+        padding-right: 10Px;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        overflow: hidden;
+        flex: 1;
+
+        &-name {
+          padding-right: 5Px;
+        }
+
+        span {
+          width: max-content;
+          font-family: 'PingFang SC';
+          font-style: normal;
+          font-weight: 400;
+          font-size: 12Px;
+          line-height: 17Px;
+          color: #666666;
+        }
+      }
+    }
+  }
+}
+.message-input-reply {
+  order: -1;
+  .reply {
+    display: flex;
+    width: 100%;
+
+    &-box {
+      flex: 1;
+      align-items: center;
+      display: flex;
+      padding: 0 10Px;
+
+      i {
+        height: 3.5rem;
+        border: 1Px solid #e8e8e9;
+      }
+
+      label {
+        cursor: pointer;
+        margin-top: 5Px;
+      }
+
+      &-show {
+        flex: 1;
+        display: flex;
+        width: 0;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        flex-direction: column;
+        justify-content: center;
+        padding-left: 6Px;
+
+        span {
+          height: 1.25rem;
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          font-size: 0.88rem;
+          color: #bfc1c5;
+          letter-spacing: 0;
+          text-overflow: ellipsis;
+          width: 100%;
+          overflow: hidden;
+        }
+      }
+    }
+  }
+}
+
+.message-input-reply-h5 {
+  order: -1;
+  flex: 1 0 100%;
+  padding-bottom: 10Px;
+  .reply {
+    &-box {
+      padding: 0;
+      i {
+        display: none;
+      }
+      &-show {
+        flex-direction: row;
+        span {
+          width: auto;
+        }
+        span:first-child {
+          padding-right: 2Px;
+        }
+        span:last-child {
+          flex: 1;
+        }
+      }
+    }
+  }
+}
+.message-input-reference-h5 {
+  order: 1;
+  flex: 1 0 100%;
+  overflow: hidden;
+  width: 100%;
+  max-width: 100%;
+  margin: 0;
+  .reference {
+    margin: 0;
+    &-box {
+      overflow: hidden;
+      padding: 0;
+      width: 100%;
+      max-width: 100%;
+      padding: 10Px;
+      margin: 5Px 0;
+
+      &-show {
+        overflow: hidden;
+        width: 0;
+        flex: 1;
+        display: flex;
+        flex-direction: row;
+        text-overflow: ellipsis;
+
+        span:last-child {
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+  }
+}
+</style>

+ 343 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/call.vue

@@ -0,0 +1,343 @@
+<template>
+  <div class="call" id="call" v-if="handleShowCallIcon(conversation, isH5)">
+    <div class="call-icon" @click="toggleShowSelectDialog">
+      <i class="icon icon-call" title="通话"></i>
+      <i class="icon icon-down-arrow" title="通话"></i>
+    </div>
+    <div
+      class="call-main"
+      :class="[isH5 && 'call-main-h5']"
+      v-show="showSelectDialog"
+    >
+      <div class="call-main-content" ref="dialog">
+        <div class="call-main-voice" @click="onClickCall(1)">
+          <i class="icon icon-call-voice" v-if="isH5"></i>
+          {{ $t('TUIChat.语音通话') }}
+        </div>
+        <div class="call-main-video" @click="onClickCall(2)">
+          <i class="icon icon-call-video" v-if="isH5"></i>
+          {{ $t('TUIChat.视频通话') }}
+        </div>
+        <footer>
+          <span
+            v-if="isH5"
+            class="close"
+            @click.stop="showSelectDialog = false"
+          >
+            {{ $t('TUIChat.取消') }}
+          </span>
+        </footer>
+      </div>
+    </div>
+    <DialogTUI
+      :show="showGroupUserDialog"
+      :isH5="isH5"
+      :isHeaderShow="false"
+      :isFooterShow="false"
+      :background="false"
+      @update:show="(e: boolean) => (showGroupUserDialog = e)"
+    >
+      <Transfer
+        :isSearch="true"
+        :title="showTitle"
+        :list="searchUserList"
+        :isH5="isH5"
+        :isRadio="conversation?.type === 'isC2C'"
+        @search="handleSearch"
+        @submit="submit"
+        @cancel="cancle"
+      />
+    </DialogTUI>
+    <DialogTUI
+      :show="showUnsupportDialog"
+      :isH5="isH5"
+      :isHeaderShow="true"
+      :isFooterShow="true"
+      :background="true"
+      :title="$t('TUIChat.欢迎使用TUICallKit')"
+      @update:show="(e: boolean) => (showUnsupportDialog = e)"
+    >
+      <div>
+        <div class="uncall-dialog-body">
+          <p>
+            {{ errorContent }}
+          </p>
+          <p v-show="Object.keys(errorLink).length > 0">
+            {{ $t('TUIChat.请点击') }}
+            <a @click="openLink(errorLink?.url)">
+              {{ $t(`TUIChat.${errorLink?.label}`) }}
+            </a>
+            {{ $t('TUIChat.进行体验') }}
+          </p>
+        </div>
+      </div>
+    </DialogTUI>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  reactive,
+  watchEffect,
+  toRefs,
+  ref,
+  watch
+} from 'vue';
+import { onClickOutside } from '@vueuse/core';
+import { Conversation } from '../../../TUIConversation/interface';
+import DialogTUI from '../../../../components/dialog';
+import Transfer from '../../../../components/transfer';
+import Link from '../../../../../utils/link/index';
+import { useStore } from 'vuex';
+
+const Call = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false
+    },
+    conversation: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  components: {
+    DialogTUI,
+    Transfer
+  },
+  setup(props: any, ctx: any) {
+    const { t } = Call.TUIServer.TUICore.config.i18n.useI18n();
+    const VuexStore =
+      ((window as any)?.TUIKitTUICore?.isOfficial && useStore && useStore()) ||
+      {};
+    const data = reactive({
+      showSelectDialog: false,
+      showCall: false,
+      showGroupUserDialog: false,
+      isH5: false,
+      isMute: false,
+      isHomeMenuOpen: false,
+      conversation: {} as Conversation,
+      showTitle: '',
+      searchUserList: [],
+      memberList: [],
+      mediaType: 0,
+      showUnsupportDialog: false,
+      errorContent: '',
+      errorLink: {}
+    });
+
+    const dialog: any = ref();
+
+    watchEffect(() => {
+      data.showSelectDialog = props.show;
+      data.isMute = props.isMute;
+      data.isH5 = props.isH5;
+      data.conversation = props.conversation;
+    });
+
+    watch(
+      () => data.showCall,
+      (newVal: boolean, oldVal: boolean) => {
+        if (newVal === oldVal) return;
+        if (data.showCall) {
+          handleCallDialogPosition();
+        }
+      }
+    );
+
+    const toggleShowSelectDialog = () => {
+      if (!data.isMute) {
+        data.showSelectDialog = true;
+      }
+    };
+
+    onClickOutside(dialog, () => {
+      data.showSelectDialog = false;
+    });
+
+    const handleCallDialogPosition = () => {
+      data.isHomeMenuOpen =
+        !!document?.getElementsByClassName('home-menu')?.length;
+    };
+
+    const onClickCall = (mediaType: number) => {
+      data.showSelectDialog = false;
+      data.mediaType = mediaType;
+      if (!Call?.TUIServer?.TUICore?.TUIServer?.TUICallKit) {
+        handleUnsupportDialog();
+        return;
+      }
+      (window as any)?.TUIKitTUICore?.isOfficial &&
+        VuexStore?.commit &&
+        VuexStore?.commit('handleTask', 6);
+      switch (data.conversation?.type) {
+        case Call.TUIServer.TUICore.TIM.TYPES.CONV_C2C:
+          handleCall(data.conversation, mediaType);
+          break;
+        case Call.TUIServer.TUICore.TIM.TYPES.CONV_GROUP:
+          handleGroupDialog(mediaType);
+          break;
+        default:
+          break;
+      }
+    };
+
+    const handleCall = async (
+      conversation: Conversation,
+      mediaType: number,
+      userIDList: Array<string> = []
+    ) => {
+      const conversationType = conversation.type;
+      switch (conversationType) {
+        case Call.TUIServer.TUICore.TIM.TYPES.CONV_C2C:
+          try {
+            await Call.TUIServer.TUICore.TUIServer.TUICallKit.call({
+              userID: conversation?.userProfile?.userID,
+              type: mediaType
+            });
+          } catch (error) {
+            handleUnsupportDialog(error);
+          }
+          break;
+        case Call.TUIServer.TUICore.TIM.TYPES.CONV_GROUP:
+          try {
+            await Call.TUIServer.TUICore.TUIServer.TUICallKit.groupCall({
+              userIDList,
+              groupID: conversation?.groupProfile?.groupID,
+              type: mediaType
+            });
+          } catch (error) {
+            handleUnsupportDialog(error);
+          }
+          break;
+        default:
+          break;
+      }
+    };
+
+    const handleShowCallIcon = (conversation: Conversation, isH5: boolean) => {
+      // 目前暂不支持H5版本 / H5 version is not currently supported
+      // if (isH5) return false;
+      // 目前暂不支持群通话 / GROUP_CALL is not currently supported
+      // if (conversation.type === Call.TUIServer.TUICore.TIM.TYPES.CONV_GROUP) return false;
+      if (
+        conversation.type === Call.TUIServer.TUICore.TIM.TYPES.CONV_C2C &&
+        conversation?.userProfile?.userID ===
+          Call?.TUIServer?.TUICore?.TUIServer?.TUIProfile?.currentStore?.profile
+            ?.userID
+      ) {
+        return false;
+      }
+      return true;
+    };
+
+    const handleGroupDialog = (mediaType: number) => {
+      data.showGroupUserDialog = true;
+      data.showTitle =
+        mediaType === 1 ? t('TUIChat.发起群语音') : t('TUIChat.发起群视频');
+      const options: any = {
+        groupID: data?.conversation?.groupProfile?.groupID,
+        count: 100,
+        offset: 0
+      };
+      Call?.TUIServer?.TUICore?.TUIServer?.TUIGroup?.getGroupMemberList(
+        options
+      )?.then((res: any) => {
+        const myUserID: any = data.conversation?.groupProfile?.selfInfo?.userID;
+        let memberList = [];
+        if (data?.conversation?.groupProfile?.muteAllMembers) {
+          memberList = [];
+        } else {
+          const time: number = new Date().getTime();
+          memberList = res.data.memberList.filter((item: any) => {
+            if (item?.userID === myUserID) return false;
+            if ((item as any)?.muteUntil * 1000 - time > 0) {
+              return false;
+            }
+            return item;
+          });
+        }
+        if (data?.conversation?.groupProfile?.muteAllMembers) {
+          memberList = [];
+        }
+        data.memberList = memberList;
+        data.searchUserList = memberList;
+      });
+    };
+
+    const handleSearch = async (val: any) => {
+      const user = data.memberList?.filter((item: any) => item?.userID === val);
+      data.searchUserList = user?.length ? user : data.memberList;
+    };
+
+    const submit = (userList: any) => {
+      data.searchUserList = data.memberList;
+      data.showGroupUserDialog = false;
+      const userIDList = userList?.map((item: any) => item?.userID);
+      handleCall(data.conversation, data.mediaType, userIDList);
+    };
+
+    const cancle = () => {
+      data.showGroupUserDialog = false;
+      data.mediaType = 0;
+    };
+
+    const openLink = (url: any) => {
+      if (url) {
+        window.open(url);
+      }
+    };
+
+    const handleUnsupportDialog = (error?: any) => {
+      if (!error) {
+        data.showUnsupportDialog = true;
+        data.errorContent = t(
+          'TUIChat.检测到您暂未集成TUICallKit,无法体验音视频通话功能'
+        );
+        data.errorLink = Link.implement;
+      } else if (
+        error?.message?.indexOf(
+          'The package you purchased does not support this ability.'
+        ) >= 0
+      ) {
+        data.showUnsupportDialog = true;
+        data.errorContent = t('TUIChat.您当前购买使用的套餐包暂未开通此功能');
+        data.errorLink = Link.purchase;
+      } else {
+        data.errorContent = error?.message || error;
+        data.showUnsupportDialog = false;
+        data.errorLink = {};
+      }
+    };
+
+    return {
+      ...toRefs(data),
+      toggleShowSelectDialog,
+      dialog,
+      onClickCall,
+      handleCallDialogPosition,
+      handleCall,
+      handleGroupDialog,
+      handleSearch,
+      submit,
+      cancle,
+      handleShowCallIcon,
+      openLink,
+      handleUnsupportDialog
+    };
+  }
+});
+export default Call;
+</script>
+
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/index.ts

@@ -0,0 +1,3 @@
+import Call from './call.vue';
+
+export default Call;

+ 45 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/style/h5.scss

@@ -0,0 +1,45 @@
+.call {
+  &-main-h5 {
+    position: fixed;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 100vw;
+    height: auto;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    padding: 0;
+    background: rgba(0, 0, 0, 0.5);
+    box-shadow: 0 2Px 12Px 0 rgb(0 0 0 / 10%);
+
+    .call-main-content {
+      border-radius: 12Px 12Px 0 0;
+      background-color: #ffffff;
+    }
+
+    .call-main-voice,
+    .call-main-video {
+      height: 30Px;
+      padding: 10Px;
+      font-size: 16Px;
+      border-bottom: 1Px solid #f7f8fa;
+      display: flex;
+      justify-content: center;
+
+      i {
+        margin-right: 10Px;
+      }
+    }
+
+    footer {
+      border-top: 10Px solid #f7f8fa;
+      height: 30Px;
+      font-size: 16Px;
+      padding: 10Px;
+      text-align: center;
+      align-items: center;
+    }
+  }
+}

+ 4 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/style/index.scss

@@ -0,0 +1,4 @@
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');
+@import './web.scss';
+@import './h5.scss';

+ 94 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/call/style/web.scss

@@ -0,0 +1,94 @@
+.call {
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+
+  &-icon {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+
+    .icon-down-arrow {
+      margin: 12Px 1Px 0;
+    }
+  }
+
+  &-main {
+    position: absolute;
+    z-index: 5;
+    background: #ffffff;
+    top: 37Px;
+    left: calc(-50% + 20Px);
+    border-radius: 2Px;
+    display: flex;
+    flex-direction: column;
+    width: 80Px;
+    border: 0.5Px solid #e0e0e0;
+    box-shadow: 0 4Px 12Px 0 rgba(0, 0, 0, 0.06);
+
+    &-voice,
+    &-video {
+      height: 34Px;
+      text-align: center;
+      font-size: 13Px;
+      line-height: 34Px;
+      align-items: center;
+    }
+
+    &-voice:hover,
+    &-video:hover {
+      background: #dceafd;
+    }
+  }
+
+  &-main:before {
+    content: '';
+    position: absolute;
+    bottom: 100%;
+    left: calc(50% - 9Px);
+    border-left: 7Px solid transparent;
+    border-right: 7Px solid transparent;
+    border-bottom: 7Px solid #e0e0e0;
+  }
+
+  &-main:after {
+    content: '';
+    position: absolute;
+    bottom: 100%;
+    left: calc(50% - 8Px);
+    border-left: 6Px solid transparent;
+    border-right: 6Px solid transparent;
+    border-bottom: 6Px solid #ffffff;
+  }
+
+  .uncall-dialog {
+    width: 500Px;
+    height: 300Px;
+    background: linear-gradient(180deg, #e9f3ff 0%, #f4f8ff 100%);
+    border-radius: 20Px;
+
+    &-body {
+      a {
+        font-family: PingFangSC-Regular;
+        font-weight: 400;
+        color: #006eff;
+        letter-spacing: 0;
+      }
+    }
+  }
+}
+
+.call-kit-container {
+  position: fixed;
+  left: calc(50% - 25rem);
+  top: calc(50% - 18rem);
+  width: 50rem;
+  height: 36rem;
+  border-radius: 16Px;
+  box-shadow: rgba(0, 0, 0, 0.16) 0Px 3Px 6Px, rgba(0, 0, 0, 0.23) 0Px 3Px 6Px;
+
+  &-left {
+    left: calc(50% - 25rem + 150Px);
+  }
+}

+ 165 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/custom/Custom.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="custom">
+    <i class="icon icon-custom" @click="toggleShow"></i>
+    <main class="custom-main" v-if="show">
+      <ul class="custom-list">
+        <li class="custom-list-item">
+          <label>data</label>
+          <input type="text" v-model="custom.data" />
+        </li>
+        <li class="custom-list-item">
+          <label>description</label>
+          <input type="text" v-model="custom.description" />
+        </li>
+        <li class="custom-list-item">
+          <label>extension</label>
+          <input type="text" v-model="custom.extension" />
+        </li>
+      </ul>
+      <ul class="custom-footer">
+        <button class="btn btn-cancel" @click="cancel">{{ $t('取消') }}</button>
+        <button
+          class="btn btn-default"
+          :disabled="!custom.data && !custom.description && custom.extension"
+          @click="submit"
+        >
+          {{ $t('发送') }}
+        </button>
+      </ul>
+    </main>
+    <div v-if="show" class="mask" @click.self="toggleShow"></div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs } from 'vue';
+
+const Custom = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      show: false,
+      custom: {
+        data: '',
+        description: '',
+        extension: ''
+      },
+      TUIServer: null
+    });
+
+    data.TUIServer = Custom.TUIServer;
+
+    watchEffect(() => {
+      data.show = props.show;
+    });
+
+    const toggleShow = () => {
+      data.show = !data.show;
+    };
+
+    const cancel = () => {
+      data.custom = {
+        data: '',
+        description: '',
+        extension: ''
+      };
+      toggleShow();
+    };
+
+    const submit = () => {
+      Custom.TUIServer.sendCustomMessage(data.custom);
+      cancel();
+    };
+
+    return {
+      ...toRefs(data),
+      toggleShow,
+      cancel,
+      submit
+    };
+  }
+});
+export default Custom;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');
+.custom {
+  display: inline-block;
+  position: relative;
+  &-main {
+    position: absolute;
+    z-index: 5;
+    width: 315Px;
+    background: #ffffff;
+    top: -180Px;
+    box-shadow: 0 2Px 12Px 0 rgba(0, 0, 0, 0.1);
+    padding: 10Px;
+    display: flex;
+    flex-direction: column;
+  }
+  &-list {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    &-item {
+      padding-bottom: 15Px;
+      label {
+        width: 88Px;
+        font-size: 18Px;
+        padding: 0 20Px;
+        display: inline-block;
+      }
+      input {
+        flex: 1;
+        height: 24Px;
+        padding: 0 10Px;
+        border: 1Px solid #dddddd;
+        border-radius: 5Px;
+      }
+    }
+  }
+  &-footer {
+    display: flex;
+    align-items: center;
+    justify-content: space-around;
+  }
+}
+.btn {
+  padding: 8Px 20Px;
+  border-radius: 4Px;
+  border: none;
+  font-weight: 400;
+  font-size: 14Px;
+  color: #ffffff;
+  letter-spacing: 0;
+  text-align: center;
+  line-height: 20Px;
+  &-cancel {
+    border: 1Px solid #dddddd;
+    color: #666666;
+  }
+  &-default {
+    background: #006eff;
+    border: 1Px solid #006eff;
+  }
+  &:disabled {
+    opacity: 0.3;
+  }
+}
+.mask {
+  position: fixed;
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  z-index: 1;
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/custom/index.ts

@@ -0,0 +1,3 @@
+import Custom from './Custom.vue';
+
+export default Custom;

+ 138 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/evaluate.vue

@@ -0,0 +1,138 @@
+<template>
+  <div class="evaluate" :class="[isH5 && 'evaluate-H5']">
+    <i class="icon icon-evaluate" title="服务评价" @click.stop="toggleShow"></i>
+    <main class="evaluate-main" v-if="show && !isMute">
+      <div class="evaluate-main-content" ref="dialog">
+        <header>
+          <aside>
+            <h1>{{ $t('Evaluate.请对本次服务进行评价') }}</h1>
+            <p v-if="isH5">
+              {{ $t('Evaluate.服务评价工具') }}({{ $t('Evaluate.使用')
+              }}<a @click="openLink(Link.customMessage)">{{ $t(`Evaluate.${Link.customMessage.label}`) }}</a
+              >{{ $t('Evaluate.搭建') }})
+            </p>
+          </aside>
+          <span v-if="isH5" class="close" @click.stop="toggleShow">关闭</span>
+        </header>
+        <div class="evaluate-content">
+          <ul class="evaluate-list">
+            <li
+              class="evaluate-list-item"
+              :class="[index < num && 'small-padding', isH5 && 'evaluate-item']"
+              v-for="(item, index) in list"
+              :key="index"
+              @click.stop="select(item, index)"
+            >
+              <i class="icon icon-star-light" v-if="index < num"></i>
+              <i class="icon icon-star" v-else></i>
+            </li>
+          </ul>
+          <textarea v-model="options.data.comment"></textarea>
+          <div class="evaluate-main-footer">
+            <button class="btn" @click="submit">{{ $t('Evaluate.提交评价') }}</button>
+          </div>
+        </div>
+        <footer v-if="!isH5">
+          {{ $t('Evaluate.服务评价工具') }}({{ $t('Evaluate.使用')
+          }}<a @click="openLink(Link.customMessage)">{{ $t(`Evaluate.${Link.customMessage.label}`) }}</a
+          >{{ $t('Evaluate.搭建') }})
+        </footer>
+      </div>
+    </main>
+  </div>
+</template>
+
+<script lang="ts">
+import { onClickOutside } from '@vueuse/core';
+import { defineComponent, reactive, watchEffect, toRefs, ref } from 'vue';
+import Link from '../../../../../utils/link';
+import constant from '../../../constant';
+import { handleOptions } from '../../utils/utils';
+
+const Evaluate = defineComponent({
+  type: 'custom',
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false,
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      link: 'https://web.sdk.qcloud.com/im/doc/zh-cn//SDK.html#createCustomMessage',
+      list: 5,
+      show: false,
+      isMute: false,
+      options: {
+        data: {
+          businessID: constant.typeEvaluate,
+          version: 1,
+          score: '',
+          comment: '',
+        },
+        description: '对本次的服务评价',
+        extension: '对本次的服务评价',
+      },
+      num: 0,
+    });
+
+    const dialog: any = ref();
+
+    onClickOutside(dialog, () => {
+      data.show = false;
+    });
+
+    watchEffect(() => {
+      data.show = props.show;
+      data.isMute = props.isMute;
+    });
+
+    const toggleShow = () => {
+      data.show = !data.show;
+      if (data.show) {
+        data.options = {
+          data: {
+            ...handleOptions(constant.typeEvaluate, 1, { score: '', comment: '' }),
+          },
+          description: '对本次的服务评价',
+          extension: '对本次的服务评价',
+        };
+        data.num = 0;
+      }
+    };
+
+    const select = (item: any, index: number) => {
+      data.num = index + 1;
+      (data.options.data as any).score = `${data.num}`;
+    };
+
+    const submit = () => {
+      Evaluate.TUIServer.sendCustomMessage(data.options);
+      toggleShow();
+    };
+    const openLink = (type: any) => {
+      window.open(type.url);
+    };
+    return {
+      ...toRefs(data),
+      toggleShow,
+      select,
+      submit,
+      dialog,
+      Link,
+      openLink,
+    };
+  },
+});
+export default Evaluate;
+</script>
+
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/index.ts

@@ -0,0 +1,3 @@
+import Evaluate from './evaluate.vue';
+
+export default Evaluate;

+ 64 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/color.scss

@@ -0,0 +1,64 @@
+.evaluate {
+  &-main {
+    background: #ffffff;
+    box-shadow: 0 2Px 12Px 0 rgba(0, 0, 0, 0.1);
+
+    header {
+      font-weight: 500;
+      color: #1c1c1c;
+    }
+
+    footer {
+      font-weight: 500;
+      color: #999999;
+
+      a {
+        color: #006eff;
+      }
+    }
+  }
+
+  &-content {
+    textarea {
+      background: #f8f8f8;
+      border: 1Px solid #ececec;
+    }
+
+    .btn {
+      background: #3370ff;
+      font-weight: 400;
+      color: #ffffff;
+      cursor: pointer;
+    }
+  }
+
+  &-list {
+    &-item {
+      font-weight: 400;
+      color: #50545c;
+    }
+  }
+
+  &-H5 {
+    &-main {
+      background: rgba(0, 0, 0, 0.5);
+
+      .evaluate-main-content {
+        background: #ffffff;
+
+        p {
+          a {
+            color: #3370ff;
+          }
+        }
+
+        .close {
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          color: #3370ff;
+          letter-spacing: 0;
+        }
+      }
+    }
+  }
+}

+ 81 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/h5.scss

@@ -0,0 +1,81 @@
+.evaluate-H5 {
+  display: inline-block;
+  position: relative;
+  cursor: auto;
+
+  .evaluate-main {
+    position: fixed;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 100vw;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    background: rgba(0, 0, 0, 0.5);
+    padding: 0;
+
+    .evaluate-main-content {
+      background: #ffffff;
+      padding: 23Px;
+
+      header {
+        display: flex;
+        justify-content: space-between;
+
+        aside {
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+          font-size: 16Px;
+          line-height: 28Px;
+
+          h1 {
+            font-size: 20Px;
+          }
+        }
+
+        .close {
+          font-size: 18Px;
+          line-height: 27Px;
+        }
+      }
+
+      .evaluate-item {
+        width: 40Px;
+        height: 24Px;
+        text-align: center;
+        cursor: auto;
+        // padding: 4Px 0;
+        font-size: 12Px;
+      }
+
+      .evaluate-content {
+        background: none;
+
+        .evaluate-list-item {
+          .icon {
+            transform: scale(1.5);
+          }
+        }
+      }
+
+      textarea {
+        font-size: 16Px;
+        width: 100%;
+      }
+    }
+
+    .evaluate-main-footer {
+      width: 100%;
+      display: flex;
+
+      .btn {
+        flex: 1;
+        padding: 14Px 0;
+        font-size: 18Px;
+      }
+    }
+  }
+}

+ 5 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/index.scss

@@ -0,0 +1,5 @@
+@import './color.scss';
+@import './web.scss';
+@import './h5.scss';
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');

+ 81 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/evaluate/style/web.scss

@@ -0,0 +1,81 @@
+.evaluate {
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+
+  .icon-evaluate {
+    margin: 12Px 10Px 0;
+  }
+
+  &-main {
+    position: absolute;
+    z-index: 5;
+    width: 315Px;
+    top: -275Px;
+    padding: 12Px;
+    display: flex;
+    flex-direction: column;
+
+    header {
+      h1 {
+        font-size: 12Px;
+        text-align: center;
+      }
+    }
+
+    footer {
+      font-size: 12Px;
+      text-align: center;
+    }
+  }
+
+  &-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 12Px 0;
+    background: url('https://web.sdk.qcloud.com/im/assets/images/login-background.png') no-repeat;
+    background-size: cover;
+    background-position-x: 128Px;
+    background-position-y: 77Px;
+
+    textarea {
+      box-sizing: border-box;
+      width: 288Px;
+      height: 90Px;
+      margin: 12Px 0;
+      padding: 12Px;
+      border-radius: 2Px;
+      resize: none;
+    }
+
+    .btn {
+      border: none;
+      border-radius: 5Px;
+      font-size: 12Px;
+      text-align: center;
+      line-height: 24Px;
+      padding: 2Px 46Px;
+      cursor: pointer;
+    }
+  }
+
+  &-list {
+    flex: 1;
+    display: flex;
+
+    &-item {
+      width: 24Px;
+      height: 24Px;
+      text-align: center;
+      cursor: pointer;
+      padding: 4Px 0;
+      font-size: 12Px;
+      padding-right: 15Px;
+
+      &:last-child {
+        padding-right: 0 !important;
+      }
+    }
+  }
+}

+ 151 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/Face.vue

@@ -0,0 +1,151 @@
+<template>
+  <div class="face" id="face">
+    <i class="icon icon-face" title="表情" @click="toggleShow"></i>
+    <main class="face-main" :class="[isH5 && 'face-H5-main']" v-show="show" ref="dialog">
+      <ul class="face-list" v-for="(item, index) in list" :key="index" v-show="currentIndex === index">
+        <li
+          class="face-list-item"
+          v-for="(childrenItem, childrenIndex) in item"
+          :key="childrenIndex"
+          @click="select(childrenItem, childrenIndex)"
+        >
+          <img v-if="index === 0" :src="emojiUrl + emojiMap[childrenItem]" />
+          <img class="face-img" v-else :src="faceUrl + childrenItem + '@2x.png'" />
+        </li>
+      </ul>
+      <ul class="face-tab">
+        <li class="face-tab-item" @click="selectFace(0)">
+          <i class="icon icon-face"></i>
+        </li>
+        <li class="face-tab-item" v-for="(item, index) in bigEmojiList" :key="index" @click="selectFace(index + 1)">
+          <img :src="faceUrl + item.list[0] + '@2x.png'" />
+        </li>
+      </ul>
+    </main>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs, computed, ref } from 'vue';
+import { emojiUrl, emojiMap, emojiName, faceUrl, bigEmojiList } from '../../utils/emojiMap';
+import { onClickOutside } from '@vueuse/core';
+import { handleErrorPrompts } from '../../../utils';
+
+const Face = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false,
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false,
+    },
+    parentID: {
+      type: String,
+      default: () => '',
+    },
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      emojiUrl,
+      emojiMap,
+      emojiName,
+      faceUrl,
+      bigEmojiList:(window as any)?.TUIKitTUICore?.isOfficial && bigEmojiList || [],
+      show: false,
+      currentIndex: 0,
+      isMute: false,
+      transDom: false,
+    });
+
+    const dialog: any = ref();
+
+    watchEffect(() => {
+      data.show = props.show;
+      data.isMute = props.isMute;
+    });
+
+    const toggleShow = () => {
+      const main: any = document.getElementsByClassName('face-main')[0];
+      if (!data.isMute) {
+        main.style.display = main.style.display === 'none' ? 'flex' : 'none';
+      }
+      if (main.style.display === 'none') {
+        selectFace(0);
+      }
+      toggleH5Show();
+    };
+
+    const toggleH5Show = () => {
+      const parent = document.getElementById(props.parentID);
+      const main = document.getElementsByClassName('face-H5-main')[0];
+      if (props.isH5) {
+        parent?.appendChild(main);
+      }
+    };
+
+    onClickOutside(dialog, () => {
+      const main: any = document.getElementsByClassName('face-main')[0];
+      if (main) {
+        main.style.display = 'none';
+      }
+    });
+
+    const select = async (item: string, index: number) => {
+      const options: any = {
+        name: item,
+      };
+      if (data.currentIndex === 0) {
+        options.type = 'emo';
+        options.url = emojiUrl + emojiMap[item];
+        options.template = `<img src="${emojiUrl + emojiMap[item]}">`;
+        if (!props.isH5) {
+          toggleShow();
+        }
+        return ctx.emit('send', options);
+      }
+      try {
+        await Face.TUIServer.sendFaceMessage({
+          // Change large expression display field
+          index: data.bigEmojiList[data.currentIndex - 1].icon,
+          data: data.bigEmojiList[data.currentIndex - 1].list[index],
+        });
+      } catch (error) {
+        handleErrorPrompts(error, props);
+      }
+      if (!props.isH5) {
+        toggleShow();
+      }
+    };
+
+    const list = computed(() => {
+      const emojiList = [data.emojiName];
+      for (let i = 0; i < data.bigEmojiList.length; i++) {
+        emojiList.push(data.bigEmojiList[i].list);
+      }
+      return emojiList;
+    });
+
+    const selectFace = (index: number) => {
+      data.currentIndex = index;
+    };
+
+    return {
+      ...toRefs(data),
+      toggleShow,
+      select,
+      selectFace,
+      list,
+      dialog,
+    };
+  },
+});
+export default Face;
+</script>
+
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/index.ts

@@ -0,0 +1,3 @@
+import Face from './Face.vue';
+
+export default Face;

+ 6 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/color.scss

@@ -0,0 +1,6 @@
+.face {
+  &-main {
+    background: #ffffff;
+    box-shadow: 0 2Px 12Px 0 rgba(0, 0, 0, 0.1);
+  }
+}

+ 7 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/h5.scss

@@ -0,0 +1,7 @@
+.face-H5 {
+  &-main {
+    position: static !important;
+    width: 100%;
+    box-shadow: none;
+  }
+}

+ 5 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/index.scss

@@ -0,0 +1,5 @@
+@import './color.scss';
+@import './web.scss';
+@import './h5.scss';
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');

+ 48 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/face/style/web.scss

@@ -0,0 +1,48 @@
+.face {
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+
+  &-main {
+    position: absolute;
+    z-index: 5;
+    width: 435Px;
+    height: 250Px;
+    top: -270Px;
+    padding: 10Px;
+    display: flex;
+    flex-direction: column;
+  }
+
+  &-list {
+    flex: 1;
+    display: flex;
+    flex-wrap: wrap;
+    overflow-y: auto;
+
+    &-item {
+      padding: 5Px;
+    }
+
+    img {
+      width: 30Px;
+    }
+
+    .face-img {
+      width: 60Px;
+    }
+  }
+
+  &-tab {
+    display: flex;
+    align-items: center;
+
+    &-item {
+      padding: 0 10Px;
+
+      img {
+        width: 30Px;
+      }
+    }
+  }
+}

+ 72 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/file/file.vue

@@ -0,0 +1,72 @@
+<template>
+  <span class="upload-btn icon icon-files">
+      <input title="文件" v-if="!isMute" type="file" data-type="file" accept="*" @change="sendUploadMessage" />
+      <slot />
+  </span>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs, watchEffect } from 'vue';
+import { handleErrorPrompts } from '../../../utils';
+
+const File = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false,
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  setup(props:any, ctx:any) {
+    const data = reactive({
+      isMute: false,
+    });
+
+    watchEffect(() => {
+      data.isMute = props.isMute;
+    });
+
+    // 发送需要上传的消息:文件
+    const sendUploadMessage = async (e:any) => {
+      if (e.target.files.length > 0) {
+        try {
+          await File.TUIServer.sendFileMessage(e.target);
+        } catch (error) {
+          handleErrorPrompts(error, props);
+        }
+      }
+      e.target.value = '';
+    };
+
+    return {
+      ...toRefs(data),
+      sendUploadMessage,
+    };
+  },
+});
+export default File;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+.upload-btn {
+  position: relative;
+  input {
+    position: absolute;
+    cursor: pointer;
+    left: 0;
+    top: 0;
+    opacity: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/file/index.ts

@@ -0,0 +1,3 @@
+import File from './file.vue';
+
+export default File;

+ 169 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/forward/forward.vue

@@ -0,0 +1,169 @@
+<template>
+  <div>
+    <div
+      class="forward"
+      :class="[isH5 ? 'forward-h5' : '']"
+      v-if="show"
+      ref="dialog"
+    >
+      <Transfer
+        title="转发"
+        :list="list"
+        :isSearch="false"
+        :isH5="isH5"
+        :isCustomItem="true"
+        :resultShow="true"
+        @submit="handleForWordMessage"
+        @cancel="toggleShow"
+      >
+        <template #left="{ data }">
+          <slot name="left" :data="data" />
+        </template>
+        <template #right="{ data }">
+          <slot name="right" :data="data" />
+        </template>
+      </Transfer>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs, ref } from 'vue';
+import Transfer from '../../../../components/transfer/index.vue';
+import { handleErrorPrompts } from '../../../utils';
+import { onClickOutside } from '@vueuse/core';
+
+const Forward = defineComponent({
+  components: {
+    Transfer
+  },
+  name: '转发',
+  props: {
+    list: {
+      type: Array,
+      default: () => []
+    },
+    message: {
+      type: Object,
+      default: () => ({})
+    },
+    show: {
+      type: Boolean,
+      default: () => false
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      list: [],
+      show: false,
+      to: -1
+    });
+
+    const dialog: any = ref();
+
+    watchEffect(() => {
+      data.list = props.list;
+      data.show = props.show;
+    });
+
+    const toggleShow = () => {
+      data.show = !data.show;
+      if (!data.show) {
+        ctx.emit('update:show', data.show);
+        data.to = -1;
+      }
+    };
+
+    onClickOutside(dialog, () => {
+      data.show = false;
+      ctx.emit('update:show', data.show);
+      data.to = -1;
+    });
+
+    const handleForWordMessage = async (list: any) => {
+      list.map(async (item: any) => {
+        try {
+          await Forward.TUIServer.forwardMessage(props.message, item);
+        } catch (error) {
+          handleErrorPrompts(error, props);
+        }
+      });
+      toggleShow();
+    };
+
+    return {
+      ...toRefs(data),
+      toggleShow,
+      handleForWordMessage,
+      dialog
+    };
+  }
+});
+export default Forward;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+.forward {
+  position: absolute;
+  z-index: 5;
+  box-sizing: border-box;
+  border-radius: 8Px;
+  padding: 15Px 20Px;
+  padding: 0;
+  left: -90Px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+  header {
+    padding: 20Px;
+    display: flex;
+    justify-content: space-between;
+  }
+  .list {
+    box-sizing: border-box;
+    margin: 0;
+    padding: 20Px;
+    width: 100%;
+    height: 100%;
+    background: #ffffff;
+    overflow-y: auto;
+    list-style: none;
+    &-item {
+      display: flex;
+      align-items: center;
+      padding: 10Px;
+      &:hover {
+        background: #dddddd;
+      }
+      .avatar {
+        width: 36Px;
+        height: 36Px;
+        border-radius: 4Px;
+        font-size: 12Px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+  }
+  footer {
+    display: flex;
+    justify-content: space-around;
+    padding: 20Px 0;
+  }
+}
+.forward-h5 {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/forward/index.ts

@@ -0,0 +1,3 @@
+import Forward from './forward.vue';
+
+export default Forward;

+ 71 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/image/image.vue

@@ -0,0 +1,71 @@
+<template>
+  <span class="upload-btn icon icon-image">
+      <input title="图片" v-if="!isMute" type="file" data-type="image" accept="image/*" @change="sendUploadMessage" />
+      <slot />
+  </span>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs, watchEffect } from 'vue';
+import { handleErrorPrompts } from '../../../utils';
+
+const Image = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false,
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  setup(props:any, ctx:any) {
+    const data = reactive({
+      isMute: false,
+    });
+
+    watchEffect(() => {
+      data.isMute = props.isMute;
+    });
+    // 发送需要上传的消息:图片
+    const sendUploadMessage = async (e:any) => {
+      if (e.target.files.length > 0) {
+        try {
+          await Image.TUIServer.sendImageMessage(e.target);
+        } catch (error) {
+          handleErrorPrompts(error, props);
+        }
+      }
+      e.target.value = '';
+    };
+
+    return {
+      ...toRefs(data),
+      sendUploadMessage,
+    };
+  },
+});
+export default Image;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+.upload-btn {
+  position: relative;
+  input {
+    position: absolute;
+    cursor: pointer;
+    left: 0;
+    top: 0;
+    opacity: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/image/index.ts

@@ -0,0 +1,3 @@
+import Image from './image.vue';
+
+export default Image;

+ 416 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/imagePreviewer/imagePreviewer.vue

@@ -0,0 +1,416 @@
+<template>
+  <div class="image-previewer" :class="[isH5 && 'image-previewer-h5']">
+    <div class="image-wrapper" ref="image">
+      <ul
+        class="image-list"
+        :style="{
+          width: `${imageList.length * 100}%`,
+          transform: `translateX(-${
+            (currentImageIndex * 100) / imageList.length
+          }%)`
+        }"
+        ref="ul"
+      >
+        <li class="image-item" v-for="(item, index) in imageList" :key="index">
+          <img
+            class="image-preview"
+            :style="{
+              transform: `scale(${zoom}) rotate(${rotate}deg)`
+            }"
+            :src="item?.payload?.imageInfoArray[0]?.url"
+          />
+        </li>
+      </ul>
+    </div>
+    <i class="icon icon-close" @click="close" v-show="!isH5" />
+    <div
+      class="image-button image-button-left"
+      v-show="!isH5 && currentImageIndex > 0"
+      @click="goPrev"
+    >
+      <i class="icon icon-left-arrow"></i>
+    </div>
+    <div
+      class="image-button image-button-right"
+      v-show="!isH5 && currentImageIndex < imageList?.length - 1"
+      @click="goNext"
+    >
+      <i class="icon icon-right-arrow"></i>
+    </div>
+    <div class="actions-bar">
+      <i class="icon icon-zoom-in" @click="zoomIn"></i>
+      <i class="icon icon-zoom-out" @click="zoomOut"></i>
+      <i class="icon icon-refresh-left" @click="rotateLeft"></i>
+      <i class="icon icon-refresh-right" @click="rotateRight"></i>
+      <span class="image-counter">
+        {{ currentImageIndex + 1 }} / {{ imageList.length }}
+      </span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import TUIEnv from '../../../../../TUIPlugin/TUIEnv';
+import {
+  defineProps,
+  ref,
+  defineEmits,
+  watchEffect,
+  onMounted,
+  onUnmounted
+} from 'vue';
+import { Message } from '../../interface';
+// import { isNumber } from '@vueuse/core';
+function isNumber(obj: any) {
+  return typeof obj === 'number' && isFinite(obj);
+}
+interface touchesPosition {
+  pageX1?: number;
+  pageY1?: number;
+  pageX2?: number;
+  pageY2?: number;
+}
+const props = defineProps({
+  imageList: {
+    type: Array,
+    default: () => [] as Array<Message>
+  },
+  currentImage: {
+    type: Object,
+    default: () => ({} as Message)
+  }
+});
+const emit = defineEmits(['close']);
+const zoom = ref(1);
+const rotate = ref(0);
+const minZoom = ref(0.1);
+const currentImageIndex = ref(0);
+const image = ref();
+const ul = ref();
+const { isH5 } = TUIEnv();
+// touch
+let startX = 0;
+let touchStore = {} as touchesPosition;
+let moveFlag = false;
+let twoTouchesFlag = false;
+let timer: number | null = null;
+
+watchEffect(() => {
+  currentImageIndex.value = props.imageList.findIndex((message: any) => {
+    return message.ID === props.currentImage.ID;
+  });
+});
+
+const debounce = (func: any, wait = 200) => {
+  let timer: any;
+  return function () {
+    if (timer) clearTimeout(timer);
+    timer = setTimeout(func, wait);
+  };
+};
+
+const handleTouchStart = (e: any) => {
+  e.preventDefault();
+  moveInit(e);
+  twoTouchesInit(e);
+};
+
+const handleTouchMove = (e: any) => {
+  e.preventDefault();
+  moveFlag = true;
+  if (e.touches && e.touches.length === 2) {
+    twoTouchesFlag = true;
+    handleTwoTouches(e);
+  }
+};
+
+const handleTouchEnd = (e: any) => {
+  e.preventDefault();
+  let moveEndX = 0;
+  let X = 0;
+  if (twoTouchesFlag) {
+    if (!timer) {
+      twoTouchesFlag = false;
+      delete touchStore.pageX2;
+      delete touchStore.pageY2;
+      timer = setTimeout(() => {
+        timer = null;
+      }, 200);
+    }
+    return;
+  }
+  // H5 touch move to left to go to prev image
+  // H5 touch move to right to go to next image
+  if (timer === null) {
+    switch (moveFlag) {
+      // touch event
+      case true:
+        moveEndX = e?.changedTouches[0]?.pageX;
+        X = moveEndX - startX;
+        if (X > 100) {
+          goPrev();
+        } else if (X < -100) {
+          goNext();
+        }
+        break;
+      // click event
+      case false:
+        close();
+        break;
+    }
+    timer = setTimeout(() => {
+      timer = null;
+    }, 200);
+  }
+};
+
+const handleTouchCancel = (e: any) => {
+  twoTouchesFlag = false;
+  delete touchStore.pageX1;
+  delete touchStore.pageY1;
+};
+
+const handleWheel = (e: any) => {
+  e.preventDefault();
+  if (Math.abs(e.deltaX) !== 0 && Math.abs(e.deltaY) !== 0) return;
+  let scale = zoom.value;
+  scale += e.deltaY * (e.ctrlKey ? -0.01 : 0.002);
+  scale = Math.min(Math.max(0.125, scale), 4);
+  zoom.value = scale;
+};
+
+const moveInit = (e: any) => {
+  startX = e?.changedTouches[0]?.pageX;
+  moveFlag = false;
+};
+
+const twoTouchesInit = (e: any) => {
+  let touch1 = e?.touches[0];
+  let touch2 = e?.touches[1];
+  touchStore.pageX1 = touch1?.pageX;
+  touchStore.pageY1 = touch1?.pageY;
+  if (touch2) {
+    touchStore.pageX2 = touch2?.pageX;
+    touchStore.pageY2 = touch2?.pageY;
+  }
+};
+
+const handleTwoTouches = (e: any) => {
+  let touch1 = e?.touches[0];
+  let touch2 = e?.touches[1];
+  if (touch2) {
+    if (!isNumber(touchStore.pageX2)) {
+      touchStore.pageX2 = touch2.pageX;
+    }
+    if (!isNumber(touchStore.pageY2)) {
+      touchStore.pageY2 = touch2.pageY;
+    }
+  }
+  const getDistance = (
+    startX: number,
+    startY: number,
+    stoPx: number,
+    stopY: number
+  ) => {
+    return Math.hypot(stoPx - startX, stopY - startY);
+  };
+  if (
+    !isNumber(touchStore.pageX1) ||
+    !isNumber(touchStore.pageY1) ||
+    !isNumber(touchStore.pageX2) ||
+    !isNumber(touchStore.pageY2)
+  ) {
+    return;
+  }
+  let touchZoom =
+    getDistance(touch1.pageX, touch1.pageY, touch2.pageX, touch2.pageY) /
+    getDistance(
+      touchStore.pageX1,
+      touchStore.pageY1,
+      touchStore.pageX2,
+      touchStore.pageY2
+    );
+  zoom.value = Math.min(Math.max(0.5, zoom.value * touchZoom), 4);
+};
+
+onMounted(() => {
+  image?.value?.addEventListener('touchstart', handleTouchStart);
+  image?.value?.addEventListener('touchmove', handleTouchMove);
+  image?.value?.addEventListener('touchend', handleTouchEnd);
+  image?.value?.addEventListener('touchcancel;', handleTouchCancel);
+  // web: mouse wheel & mac wheel
+  image?.value?.addEventListener('wheel', handleWheel);
+  // web: close on esc keydown
+  document?.addEventListener('keydown', handleEsc);
+});
+
+const handleEsc = (e: any) => {
+  e.preventDefault();
+  if (e?.keyCode === 27) {
+    close();
+  }
+};
+const zoomIn = () => {
+  zoom.value += 0.1;
+};
+const zoomOut = () => {
+  zoom.value =
+    zoom.value - 0.1 > minZoom.value ? zoom.value - 0.1 : minZoom.value;
+};
+const close = () => {
+  emit('close');
+};
+const rotateLeft = () => {
+  rotate.value -= 90;
+};
+const rotateRight = () => {
+  rotate.value += 90;
+};
+const goNext = () => {
+  ul.value.style.transition = '0.5s';
+  currentImageIndex.value < props.imageList.length - 1 &&
+    currentImageIndex.value++;
+  initStyle();
+};
+const goPrev = () => {
+  ul.value.style.transition = '0.5s';
+  currentImageIndex.value > 0 && currentImageIndex.value--;
+  initStyle();
+};
+const initStyle = () => {
+  zoom.value = 1;
+  rotate.value = 0;
+};
+
+onUnmounted(() => {
+  image?.value?.removeEventListener('touchstart', handleTouchStart);
+  image?.value?.removeEventListener('touchmove', handleTouchMove);
+  image?.value?.removeEventListener('touchend', handleTouchEnd);
+  image?.value?.removeEventListener('touchcancel;', handleTouchCancel);
+  image?.value?.removeEventListener('wheel', handleWheel);
+  document?.removeEventListener('keydown', handleEsc);
+});
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+.image-previewer {
+  position: fixed;
+  z-index: 12;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(#000000, 0.3);
+  top: 0;
+  left: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  .image-wrapper {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    overflow: hidden;
+  }
+  .image-list {
+    position: absolute;
+    height: 100%;
+    left: 0;
+    padding: 0;
+    margin: 0;
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-content: center;
+    .image-item {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      overflow: hidden;
+    }
+  }
+  .image-preview {
+    max-width: 100%;
+    max-height: 100%;
+    transition: transform 0.1s ease 0s;
+    pointer-events: auto;
+  }
+  .image-button {
+    position: absolute;
+    cursor: pointer;
+    width: 40Px;
+    height: 40Px;
+    border-radius: 50%;
+    top: calc(50% - 20Px);
+    background: rgba(255, 255, 255, 0.8);
+    &-left {
+      left: 10Px;
+    }
+    &-right {
+      right: 10Px;
+    }
+    .icon {
+      position: absolute;
+      bottom: 0;
+      top: 0;
+      left: 0;
+      right: 0;
+      margin: auto;
+      line-height: 40Px;
+    }
+  }
+  .icon-close {
+    position: absolute;
+    cursor: pointer;
+    width: 40Px;
+    height: 40Px;
+    border-radius: 50%;
+    top: 3%;
+    right: 3%;
+    padding: 6Px;
+    background: rgba(255, 255, 255, 0.8);
+    &::before,
+    &::after {
+      background-color: #444444;
+    }
+  }
+}
+.image-previewer-h5 {
+  width: 100%;
+  height: 100%;
+  background: #000000;
+  display: flex;
+  flex-direction: column;
+}
+.actions-bar {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  position: absolute;
+  bottom: 5%;
+  padding: 12Px;
+  border-radius: 6Px;
+  background: rgba(255, 255, 255, 0.8);
+  .icon {
+    position: static;
+    font-size: 24Px;
+    cursor: pointer;
+    margin: 0 6Px;
+    width: 27Px;
+    height: 27Px;
+    margin: 5Px;
+  }
+}
+.image-counter {
+  background: rgba(20, 18, 20, 0.53);
+  padding: 3Px 5Px;
+  margin: 5Px;
+  border-radius: 3Px;
+  color: #fff;
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/imagePreviewer/index.ts

@@ -0,0 +1,3 @@
+import ImagePreviewer from './imagePreviewer.vue';
+
+export default ImagePreviewer;

+ 162 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/location/Location.vue

@@ -0,0 +1,162 @@
+<template>
+  <div class="location">
+    <i class="icon icon-location" @click="toggleShow"></i>
+    <main class="location-main" v-if="show">
+      <ul class="location-list">
+        <li class="location-list-item">
+          <label>{{ $t('TUIChat.描述') }}</label>
+          <input type="text" v-model="location.description" />
+        </li>
+        <li class="location-list-item">
+          <label>{{ $t('TUIChat.经度') }}</label>
+          <input type="number" v-model="location.longitude" />
+        </li>
+        <li class="location-list-item">
+          <label>{{ $t('TUIChat.纬度') }}</label>
+          <input type="number" v-model="location.latitude" />
+        </li>
+      </ul>
+      <ul class="location-footer">
+        <button class="btn btn-cancel" @click="cancel">{{ $t('取消') }}</button>
+        <button
+          class="btn btn-default"
+          :disabled="
+            !location.data && !location.description && location.extension
+          "
+          @click="submit"
+        >
+          {{ $t('发送') }}
+        </button>
+      </ul>
+    </main>
+    <div v-if="show" class="mask" @click.self="toggleShow"></div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs } from 'vue';
+
+const Location = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false
+    }
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      show: false,
+      location: {
+        description: '深圳市深南大道10000号腾讯大厦',
+        longitude: 113.941079,
+        latitude: 22.546103
+      }
+    });
+
+    watchEffect(() => {
+      data.show = props.show;
+    });
+
+    const toggleShow = () => {
+      data.show = !data.show;
+    };
+
+    const cancel = () => {
+      toggleShow();
+    };
+
+    const submit = () => {
+      Location.TUIServer.sendLocationMessage(data.location);
+      cancel();
+    };
+
+    return {
+      ...toRefs(data),
+      toggleShow,
+      cancel,
+      submit
+    };
+  }
+});
+export default Location;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+.location {
+  display: inline-block;
+  position: relative;
+  &-main {
+    position: absolute;
+    z-index: 5;
+    width: 315Px;
+    background: #ffffff;
+    top: -180Px;
+    box-shadow: 0 2Px 12Px 0 rgba(0, 0, 0, 0.1);
+    padding: 10Px;
+    display: flex;
+    flex-direction: column;
+  }
+  &-list {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    &-item {
+      padding-bottom: 15Px;
+      label {
+        width: 88Px;
+        font-size: 18Px;
+        padding: 0 20Px;
+        display: inline-block;
+      }
+      input {
+        flex: 1;
+        height: 24Px;
+        padding: 0 10Px;
+        border: 1Px solid #dddddd;
+        border-radius: 5Px;
+        &::-webkit-inner-spin-button {
+          display: none;
+        }
+      }
+    }
+  }
+  &-footer {
+    display: flex;
+    align-items: center;
+    justify-content: space-around;
+  }
+}
+.btn {
+  padding: 8Px 20Px;
+  border-radius: 4Px;
+  border: none;
+  font-weight: 400;
+  font-size: 14Px;
+  color: #ffffff;
+  letter-spacing: 0;
+  text-align: center;
+  line-height: 20Px;
+  &-cancel {
+    border: 1Px solid #dddddd;
+    color: #666666;
+  }
+  &-default {
+    background: #006eff;
+    border: 1Px solid #006eff;
+  }
+  &:disabled {
+    opacity: 0.3;
+  }
+}
+.mask {
+  position: fixed;
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  left: 0;
+  opacity: 0;
+  z-index: 1;
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/location/index.ts

@@ -0,0 +1,3 @@
+import Location from './Location.vue';
+
+export default Location;

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/index.ts

@@ -0,0 +1,3 @@
+import ReadReceiptDialog from './readReceiptDialog.vue';
+
+export default ReadReceiptDialog;

+ 339 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/readReceiptDialog.vue

@@ -0,0 +1,339 @@
+<template>
+  <div
+    class="read-receipt"
+    :class="[isH5 ? 'read-receipt-H5' : '', isMenuOpen ? 'read-receipt-menu-open' : '']"
+    v-if="show"
+    ref="dialog"
+  >
+    <div class="header">
+      <div class="header-back">
+        <i v-if="isH5" class="icon icon-back" @click="toggleShow"></i>
+      </div>
+      <div class="header-title">
+        <span>{{ $t('TUIChat.消息详情') }}</span>
+      </div>
+      <div class="header-close">
+        <i v-if="!isH5" class="icon icon-close" @click="toggleShow"></i>
+      </div>
+    </div>
+    <div class="body">
+      <div v-if="isH5" class="body-message">
+        <div class="message">
+          <div class="message-info">
+            <span>{{ message.from }}</span>
+            <span>{{ caculateTimeago(message.time * 1000) }}</span>
+          </div>
+          <div class="message-cont">
+            <img v-if="messageInfo.isImg" class="message-cont-img" :src="messageInfo.content" />
+            <p v-else>{{ messageInfo.content }}</p>
+          </div>
+        </div>
+      </div>
+      <div class="body-tab">
+        <template v-for="(val, key) in readReceiptList">
+          <div
+            class="tab-item"
+            :class="key === showListNow && 'tab-item-now'"
+            v-if="val.show"
+            :key="key"
+            @click="showListNow = key"
+          >
+            <div class="tab-item-title">{{ val?.label }}</div>
+            <div class="tab-item-count">{{ val?.count }}</div>
+          </div>
+        </template>
+      </div>
+      <div class="body-list">
+        <ul>
+          <li v-for="(item, index) in readReceiptList[showListNow].userList" :key="index" class="body-list-item">
+            <img
+              class="avatar"
+              :src="item?.avatar || 'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+              onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+            />
+            <div class="name">
+              {{ item?.nick || item?.userID }}
+            </div>
+          </li>
+        </ul>
+        <div class="more" v-if="!readReceiptList[showListNow].isCompleted" @click="getMoreList">查看更多</div>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs, watch, ref } from 'vue';
+import { onClickOutside } from '@vueuse/core';
+import { caculateTimeago, handleErrorPrompts } from '../../../utils';
+import { Message, userListItem } from '../../interface';
+import {
+  handleImageMessageShowContext,
+  handleVideoMessageShowContext,
+  handleFaceMessageShowContext,
+} from '../../utils/utils';
+import { TUIEnv } from '../../../../../TUIPlugin';
+const ReadReceiptDialog = defineComponent({
+  type: 'custom',
+  props: {
+    message: {
+      type: Object,
+      default: () => ({}),
+    },
+    isH5: {
+      type: Boolean,
+      default: false,
+    },
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  setup(props: any, ctx: any) {
+    const { t } = (window as any).TUIKitTUICore.config.i18n.useI18n();
+    const data = reactive({
+      message: {} as Message,
+      isGroup: false,
+      show: false,
+      isH5: false,
+      messageInfo: {
+        isImg: false,
+        content: '',
+      },
+      readReceiptList: [
+        {
+          label: props.isH5 ? t('TUIChat.人已读') : t('TUIChat.已读'),
+          count: 0,
+          userList: [] as userListItem[],
+          isCompleted: true,
+          cursor: '',
+          show: true,
+        },
+        {
+          label: props.isH5 ? t('TUIChat.人未读') : t('TUIChat.未读'),
+          count: 0,
+          userList: [] as userListItem[],
+          isCompleted: true,
+          cursor: '',
+          show: true,
+        },
+        {
+          label: props.isH5 ? t('TUIChat.人关闭阅读状态') : t('TUIChat.关闭阅读状态'),
+          count: 0,
+          userList: [] as userListItem[],
+          isCompleted: true,
+          cursor: '',
+          show: false,
+        },
+      ],
+      showListNow: 0,
+      isMenuOpen: true,
+      env: TUIEnv(),
+    });
+
+    const dialog: any = ref();
+
+    watchEffect(() => {
+      data.show = props.show;
+      data.isH5 = props.isH5;
+    });
+
+    watch(
+      () => {
+        props.message, data.show;
+      },
+      async () => {
+        if (!data.show) return;
+        handleDialogPosition();
+        data.message = props.message;
+        isGroup();
+        showMessage();
+        data.readReceiptList[0].count = data.message?.readReceiptInfo?.readCount || data.readReceiptList[0].count;
+        data.readReceiptList[1].count = data.message?.readReceiptInfo?.unreadCount || data.readReceiptList[1].count;
+        getReadList();
+        getUnreadList();
+      },
+      { deep: true }
+    );
+
+    const toggleShow = () => {
+      data.show = !data.show;
+      if (!data.show) {
+        ctx.emit('closeDialog', 'receipt');
+        close();
+      }
+    };
+
+    onClickOutside(dialog, () => {
+      data.show = false;
+      ctx.emit('closeDialog', 'receipt');
+      close();
+    });
+
+    const getReadList = async (more = false) => {
+      if (!data.isGroup || !data.message || Object.keys(data.message).length === 0) return;
+      try {
+        const obj = await ReadReceiptDialog.TUIServer.getGroupReadMemberList(
+          data.message,
+          more ? data.readReceiptList[0].cursor : ''
+        );
+        data.readReceiptList[0].isCompleted = obj?.data?.isCompleted;
+        data.readReceiptList[0].cursor = obj?.data?.cursor || '';
+        const list = obj.data.readUserIDList;
+        const readList: userListItem[] = await handleAvatarAndName(list);
+        data.readReceiptList[0].userList = more
+          ? ([...data.readReceiptList[0].userList, ...readList] as userListItem[])
+          : readList;
+      } catch (error) {
+        if (error && (error as any)?.code === 10062) {
+          const message = t('TUIChat.您当前购买使用的套餐包暂未开通群消息已读回执功能');
+          handleErrorPrompts(message, data.env);
+          console.warn(message);
+        }
+      }
+    };
+
+    const getUnreadList = async (more = false) => {
+      if (!data.isGroup || !data.message || Object.keys(data.message).length === 0) return;
+      const obj = await ReadReceiptDialog.TUIServer.getGroupUnreadMemberList(
+        data.message,
+        more ? data.readReceiptList[1].cursor : ''
+      );
+      data.readReceiptList[1].isCompleted = obj?.data.isCompleted;
+      data.readReceiptList[1].cursor = obj?.data?.cursor || '';
+      const list = obj.data.unreadUserIDList;
+      const unreadList: userListItem[] = await handleAvatarAndName(list);
+      data.readReceiptList[1].userList = more
+        ? ([...data.readReceiptList[1].userList, ...unreadList] as userListItem[])
+        : unreadList;
+    };
+
+    const handleAvatarAndName = async (list: any) => {
+      const avatarAndNameList: userListItem[] = [];
+      if (list.length && data.isGroup) {
+        const obj = await ReadReceiptDialog.TUIServer.getUserProfile(list);
+        const userProfileList = obj.data;
+        userProfileList.forEach((item: any) => {
+          avatarAndNameList.push({
+            nick: item?.nick,
+            avatar: item?.avatar,
+            userID: item?.userID,
+          });
+        });
+      }
+      return avatarAndNameList;
+    };
+
+    const isGroup = () => {
+      if (data.message?.conversationType === ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.CONV_GROUP) {
+        data.isGroup = true;
+      } else {
+        data.isGroup = false;
+      }
+      return;
+    };
+
+    const getMoreList = () => {
+      switch (data.showListNow) {
+        case 0:
+          getReadList(true);
+          break;
+        case 1:
+          getUnreadList(true);
+          break;
+        default:
+          break;
+      }
+    };
+
+    const close = () => {
+      data.message = {};
+      data.readReceiptList = [
+        {
+          label: props.isH5 ? t('TUIChat.人已读') : t('TUIChat.已读'),
+          count: 0,
+          userList: [] as userListItem[],
+          isCompleted: true,
+          cursor: '',
+          show: true,
+        },
+        {
+          label: props.isH5 ? t('TUIChat.人未读') : t('TUIChat.未读'),
+          count: 0,
+          userList: [] as userListItem[],
+          isCompleted: true,
+          cursor: '',
+          show: true,
+        },
+        {
+          label: props.isH5 ? t('TUIChat.人关闭阅读状态') : t('TUIChat.关闭阅读状态'),
+          count: 0,
+          userList: [] as userListItem[],
+          isCompleted: true,
+          cursor: '',
+          show: false,
+        },
+      ];
+      data.showListNow = 0;
+      data.messageInfo = {
+        isImg: false,
+        content: '',
+      };
+    };
+
+    const showMessage = () => {
+      if (!data.message || !data.isH5) return;
+      switch ((data.message as any).type) {
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_TEXT:
+          data.messageInfo.content = data.message?.payload?.text;
+          data.messageInfo.isImg = false;
+          break;
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_CUSTOM:
+          data.messageInfo.content = t('TUIChat.自定义');
+          data.messageInfo.isImg = false;
+          break;
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_IMAGE:
+          data.messageInfo.content = handleImageMessageShowContext(data.message)?.url;
+          data.messageInfo.isImg = true;
+          break;
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_AUDIO:
+          data.messageInfo.content = t('TUIChat.语音');
+          data.messageInfo.isImg = false;
+          break;
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_VIDEO:
+          data.messageInfo.content = handleVideoMessageShowContext(data.message)?.snapshotUrl;
+          data.messageInfo.isImg = true;
+          break;
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_FILE:
+          data.messageInfo.content = t('TUIChat.文件') + data.message?.payload?.fileName;
+          data.messageInfo.isImg = false;
+          break;
+        case ReadReceiptDialog.TUIServer.TUICore.TIM.TYPES.MSG_FACE:
+          data.messageInfo.content = handleFaceMessageShowContext(data.message)?.url;
+          data.messageInfo.isImg = true;
+          break;
+      }
+    };
+
+    const handleDialogPosition = () => {
+      data.isMenuOpen = !!document?.getElementsByClassName('home-menu')?.length;
+    };
+
+    return {
+      ...toRefs(data),
+      dialog,
+      toggleShow,
+      getReadList,
+      getUnreadList,
+      isGroup,
+      handleAvatarAndName,
+      close,
+      getMoreList,
+      showMessage,
+      caculateTimeago,
+      handleDialogPosition,
+    };
+  },
+});
+export default ReadReceiptDialog;
+</script>
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 141 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/style/h5.scss

@@ -0,0 +1,141 @@
+.read-receipt-H5 {
+  z-index: 5;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  transform: none;
+  box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+  background: #ffffff;
+  border-radius: 8Px;
+  overflow: hidden;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  padding: 0;
+
+  .header {
+    padding-left: 20Px;
+    padding-right: 20Px;
+  }
+
+  .body {
+    padding: 0;
+
+    .body-tab,
+    .body-list {
+      width: 100%;
+    }
+
+    .body-list-item {
+      background-color: #ffffff;
+      padding-left: 10Px;
+      display: flex;
+      flex-direction: row;
+
+      .avatar {
+        border-radius: 10%;
+        padding: 0;
+        margin: 10Px;
+      }
+
+      .name {
+        width: calc(100% - 40Px);
+        height: 56Px;
+        line-height: 56Px;
+        border-bottom: 1Px solid #efefef;
+      }
+    }
+
+    .body-tab {
+      height: 22Px;
+      padding: 15Px;
+      width: calc(100% - 30Px);
+      border-bottom: none;
+
+      .tab-item {
+        display: flex;
+        flex-direction: row-reverse;
+        justify-content: center;
+        align-items: center;
+        font-size: 16Px;
+        height: 22Px;
+        font-family: PingFangSC-Medium;
+
+        &-title,
+        &-count {
+          height: 22Px;
+          font-size: 15Px;
+          letter-spacing: 0;
+          text-align: center;
+          line-height: 20Px;
+          font-weight: 400;
+          color: #8d8d8d;
+        }
+
+        &-count {
+          font-size: 17Px;
+          font-weight: 800;
+        }
+
+        &-now {
+          color: #147aff;
+          padding-bottom: 3Px;
+          border-bottom: 2.6Px solid #147aff;
+
+          .tab-item-title,
+          .tab-item-count {
+            color: #147aff;
+          }
+        }
+      }
+    }
+
+    .body-message {
+      background-color: #f7f8fa;
+
+      .message {
+        margin: 10Px 0;
+        background-color: #ffffff;
+        padding: 5Px;
+
+        .message-info {
+          span {
+            padding-left: 10Px;
+            padding-right: 10Px;
+            width: 94Px;
+            height: 30Px;
+            font-family: PingFangSC-Regular;
+            font-weight: 400;
+            font-size: 12Px;
+            color: #999999;
+            letter-spacing: 0;
+            text-align: center;
+            line-height: 30Px;
+          }
+        }
+
+        .message-cont {
+          padding: 0 10Px 10Px 10Px;
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          font-size: 14Px;
+          color: #111111;
+          letter-spacing: 0;
+          max-height: 100Px;
+
+          p {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+
+          &-img {
+            max-height: 70Px;
+          }
+        }
+      }
+    }
+  }
+}

+ 4 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/style/index.scss

@@ -0,0 +1,4 @@
+@import './web.scss';
+@import './h5.scss';
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');

+ 164 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/readReceiptDialog/style/web.scss

@@ -0,0 +1,164 @@
+.read-receipt {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 5;
+  box-sizing: border-box;
+  padding: 20Px;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+  width: 368Px;
+  height: 510Px;
+  background: #ffffff;
+  border-radius: 8Px;
+  overflow: hidden;
+
+  &-menu-open {
+    left: calc(50% + 150Px);
+  }
+
+  .header {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    height: 30Px;
+    padding: 10Px 0;
+
+    &-title {
+      width: calc(100% - 40Px);
+      text-align: center;
+      height: 30Px;
+      font-family: PingFangSC-Medium;
+      font-weight: 500;
+      font-size: 16Px;
+      color: #333333;
+      line-height: 30Px;
+    }
+
+    &-close {
+      width: 20Px;
+
+      i {
+        width: 12Px;
+        height: 12Px;
+      }
+    }
+
+    &-back {
+      width: 20Px;
+    }
+  }
+
+  .body {
+    padding: 10Px 0;
+    width: 100%;
+    flex: 1 1 auto;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    .body-tab {
+      padding: 10Px 0;
+      width: 100%;
+      height: 55Px;
+      display: flex;
+      flex-direction: row;
+      justify-content: space-around;
+      border-bottom: 1Px solid #e8e8e9;
+
+      .tab-item {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+
+        &-title {
+          height: 20Px;
+          opacity: 0.8;
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          font-size: 14Px;
+          color: #000000;
+        }
+
+        &-count {
+          height: 35Px;
+          opacity: 0.8;
+          font-family: DINAlternate-Bold;
+          font-weight: Bold;
+          font-size: 30Px;
+          color: #000000;
+        }
+
+        &-now {
+          color: #679ce1;
+
+          .tab-item-title,
+          .tab-item-count {
+            color: #679ce1;
+          }
+        }
+      }
+    }
+
+    .body-list {
+      width: 100%;
+      flex: 1 1 auto;
+      overflow-y: auto;
+
+      &-item {
+        display: flex;
+        flex: 1 1 auto;
+        flex-direction: row;
+        flex-wrap: nowrap;
+        align-items: center;
+
+        .avatar {
+          width: 36Px;
+          height: 36Px;
+          margin: 10Px 10Px 10Px 0Px;
+          border-radius: 10%;
+        }
+
+        .name {
+          width: calc(100% - 60Px);
+          height: 20Px;
+          display: inline-block;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          line-height: 20Px;
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          font-size: 14Px;
+          color: #000000;
+          letter-spacing: 0;
+        }
+      }
+
+      .more {
+        text-align: center;
+        color: rgba(0, 0, 0, 0.3);
+        font-size: 12Px;
+        padding: 10Px;
+      }
+    }
+  }
+}
+
+::-webkit-scrollbar {
+  width: 4Px;
+  height: 140Px;
+  background-color: transparent;
+}
+
+::-webkit-scrollbar-track {
+  border-radius: 10Px;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 10Px;
+  background-color: #d8d8d8;
+}

+ 2 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/index.ts

@@ -0,0 +1,2 @@
+import Replies from './replies.vue'
+export default Replies

+ 312 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/replies-item.vue

@@ -0,0 +1,312 @@
+<template>
+  <div class="replies-item" :class="!isRoot ? 'replies-item-normal' : ''">
+    <div class="message-bubble" ref="htmlRefHook">
+      <img
+        class="avatar"
+        :src="
+          message?.avatar ||
+          'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'
+        "
+        onerror="this.src='https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png'"
+      />
+      <main class="message-area">
+        <div class="message-area-title">
+          <label class="name">
+            {{
+              isRoot
+                ? message.nameCard || message.nick || message.from
+                : message.messageSender
+            }}
+          </label>
+          <label class="time">
+            {{
+              caculateTimeago(
+                (isRoot ? message?.time : message?.messageTime) * 1000
+              )
+            }}
+          </label>
+        </div>
+        <div class="content content-in">
+          <MessageText
+            v-if="
+              message.messageType === constant.typeText ||
+              message.type === TIM.TYPES.MSG_TEXT ||
+              !isRoot
+            "
+            :data="
+              handleTextMessageShowContext(
+                isRoot
+                  ? message
+                  : { payload: { text: message?.messageAbstract } }
+              )
+            "
+          />
+          <span
+            v-if="
+              message.messageType === constant.typeCustom ||
+              message.type === TIM.TYPES.MSG_CUSTOM
+            "
+          >
+            {{ handleCustomMessageShowContext(message)?.custom }}
+          </span>
+          <img
+            v-if="
+              message.messageType === constant.typeImage ||
+              message.type === TIM.TYPES.MSG_IMAGE
+            "
+            class="message-img"
+            :src="message?.payload?.imageInfoArray[1].url"
+          />
+          <div
+            v-if="
+              message.messageType === constant.typeAudio ||
+              message.type === TIM.TYPES.MSG_AUDIO
+            "
+            class="message-audio"
+            :style="`width: ${message?.payload?.second * 10 + 40}Px`"
+          >
+            <i class="icon icon-voice"></i>
+            <label>{{ message?.payload?.second }}s</label>
+          </div>
+          <div
+            v-if="
+              message.messageType === constant.typeVideo ||
+              message.type === TIM.TYPES.MSG_VIDEO
+            "
+            class="message-video-cover"
+          >
+            <img
+              class="message-videoimg"
+              :src="message?.payload?.snapshotUrl || message?.payload?.thumbUrl"
+            />
+          </div>
+          <img
+            v-if="
+              message.messageType === constant.typeFace ||
+              message.type === TIM.TYPES.MSG_FACE
+            "
+            class="message-img"
+            :src="url"
+          />
+          <div
+            v-if="
+              message.messageType === constant.typeFile ||
+              message.type === TIM.TYPES.MSG_FILE
+            "
+            class="message-file"
+          >
+            <i class="icon icon-files"></i>
+            <div class="message-file-content">
+              <label>{{ message?.payload?.fileName }}</label>
+              <span>
+                {{
+                  handleFileMessageShowContext(message)?.size ||
+                  message?.payload?.fileSize
+                }}
+              </span>
+            </div>
+          </div>
+          <MessageEmojiReact :message="message" type="content" />
+        </div>
+      </main>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { defineComponent, reactive, toRefs, watchEffect } from 'vue';
+import { Message } from '../../interface';
+import {
+  handleTextMessageShowContext,
+  handleCustomMessageShowContext,
+  handleFileMessageShowContext
+} from '../../utils/utils';
+import { MessageText, MessageEmojiReact } from '../../components';
+import { caculateTimeago } from '../../../utils';
+import constant from '../../../constant';
+import TIM from '../../../../../TUICore/tim/index';
+const RepliesItem = defineComponent({
+  props: {
+    message: {
+      type: Object,
+      default: () => ({})
+    },
+    isH5: {
+      type: Boolean,
+      default: false
+    },
+    isRoot: {
+      type: Boolean,
+      default: false
+    }
+  },
+  components: {
+    MessageText,
+    MessageEmojiReact
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      message: {} as Message,
+      isH5: false,
+      url: '',
+      isRoot: false,
+      constant,
+      TIM
+    });
+    watchEffect(() => {
+      data.message = props.message;
+      data.isH5 = props.isH5;
+      data.isRoot = props.isRoot;
+      if (data.message.type === TIM.TYPES.MSG_FACE) {
+        data.url = `https://web.sdk.qcloud.com/im/assets/face-elem/${data.message?.payload?.data}@2x.png`;
+      }
+    });
+    return {
+      ...toRefs(data),
+      handleTextMessageShowContext,
+      handleCustomMessageShowContext,
+      handleFileMessageShowContext,
+      caculateTimeago
+    };
+  }
+});
+export default RepliesItem;
+</script>
+<style lang="scss" scoped>
+.replies-item {
+  padding: 15Px;
+  width: auto;
+  &-normal {
+    padding: 12.8Px 15Px 0 17Px;
+    .message-bubble {
+      padding-bottom: 0;
+      .avatar {
+        width: 48Px;
+        height: 48Px;
+        border-radius: 8Px;
+      }
+      .message-area {
+        padding: 0 0 8Px 0;
+        margin: 0 0 0 16Px;
+        border-bottom: 0.1Px solid #dbdbdb;
+        &-title {
+          .name {
+            font-size: 14Px;
+            line-height: 20Px;
+          }
+          .time {
+            font-size: 12Px;
+            line-height: 18Px;
+          }
+        }
+        .content,
+        .content-in {
+          background: inherit;
+          border-radius: 0;
+          padding: 0;
+          line-height: 22Px;
+        }
+      }
+    }
+  }
+  .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-area {
+    max-width: calc(100% - 54Px);
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    padding: 0 8Px;
+    flex: 1;
+    &-title {
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+      .name,
+      .time {
+        padding-bottom: 4Px;
+        font-weight: 400;
+        font-size: 0.8rem;
+        color: #999999;
+        letter-spacing: 0;
+      }
+      .name {
+        flex: 1;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+    }
+    .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: #f2f2f2;
+        border-radius: 0Px 10Px 10Px 10Px;
+        .message-img,
+        .message-videoimg {
+          width: inherit;
+          height: inherit;
+          max-height: 100%;
+          max-width: 100%;
+          max-height: 300Px;
+        }
+      }
+    }
+  }
+  .message-label {
+    align-self: flex-end;
+    font-family: PingFangSC-Regular;
+    font-weight: 400;
+    font-size: 12Px;
+    color: #b6b8ba;
+    word-break: keep-all;
+  }
+  .message-file {
+    flex: 1;
+    display: flex;
+    cursor: pointer;
+    .message-file-content {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+    }
+  }
+
+  .message-audio {
+    display: flex;
+    align-items: center;
+    position: relative;
+    cursor: pointer;
+    max-width: 100%;
+    overflow: hidden;
+    .icon {
+      margin: 0 4Px;
+    }
+    audio {
+      width: 0;
+      height: 0;
+    }
+  }
+  .reserve {
+    flex-direction: row-reverse;
+  }
+}
+</style>

+ 156 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/replies.vue

@@ -0,0 +1,156 @@
+<template>
+  <div
+    class="replies"
+    :class="[isH5 ? 'replies-H5' : '', isMenuOpen ? 'replies-menu-open' : '']"
+    v-if="show"
+    ref="dialog"
+  >
+    <div class="header">
+      <div class="header-back">
+        <i v-if="isH5" class="icon icon-back" @click="toggleShow"></i>
+      </div>
+      <div class="header-title">
+        <span>{{ $t('TUIChat.回复详情') }}</span>
+      </div>
+      <div class="header-close">
+        <i v-if="!isH5" class="icon icon-close" @click="toggleShow"></i>
+      </div>
+    </div>
+    <div class="body">
+      <div class="body-message">
+        <RepliesItem :message="message" :isH5="isH5" :isRoot="true" />
+      </div>
+      <div class="body-list">
+        <ul>
+          <li v-for="(item, index) in replies" :key="index" class="body-list-item">
+            <RepliesItem :message="item" :isH5="isH5" :isRoot="false" />
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs, watch, ref } from 'vue';
+import { onClickOutside } from '@vueuse/core';
+import { caculateTimeago } from '../../../utils';
+import { Message } from '../../interface';
+import TIM from '../../../../../TUICore/tim';
+import RepliesItem from './replies-item.vue';
+import { JSONToObject } from '../../utils/utils';
+const ReadReceiptDialog = defineComponent({
+  type: 'custom',
+  components: {
+    RepliesItem,
+  },
+  props: {
+    message: {
+      type: Object,
+      default: () => ({}),
+    },
+    isH5: {
+      type: Boolean,
+      default: false,
+    },
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    url: {
+      type: String,
+      default: '',
+    },
+    messageList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      message: {} as Message,
+      isGroup: false,
+      show: false,
+      isH5: false,
+      url: '',
+      showListNow: 0,
+      isMenuOpen: true,
+      replies: [],
+      messageList: [],
+      TIM,
+    });
+
+    const dialog: any = ref();
+
+    watchEffect(() => {
+      data.message = props.message;
+      data.show = props.show;
+      data.isH5 = props.isH5;
+      data.url = props.url;
+      data.messageList = props.messageList;
+    });
+
+    watch(
+      () => {
+        data.message, data.messageList;
+      },
+      () => {
+        data.message = props.message;
+        data.messageList = props.messageList;
+        handleReplies(data.message);
+      },
+      { deep: true }
+    );
+
+    const toggleShow = () => {
+      data.show = !data.show;
+      if (!data.show) {
+        ctx.emit('closeDialog', 'replies');
+        close();
+      }
+    };
+
+    onClickOutside(dialog, () => {
+      data.show = false;
+      ctx.emit('closeDialog', 'replies');
+      close();
+    });
+
+    const handleReplies = (message: Message) => {
+      try {
+        const { cloudCustomData } = message;
+        if (!cloudCustomData) return;
+        const cloudCustomObject = JSONToObject(cloudCustomData);
+        data.replies = cloudCustomObject?.messageReplies?.replies;
+        data?.replies?.forEach((item: any) => {
+          const { messageID, messageSender } = item;
+          const message = data.messageList.find((item: Message) => 
+            (item.ID === messageID || item.from === messageSender)
+          );
+          item.avatar = message ? (message as Message)?.avatar : '';
+        });
+      } catch (err) {
+        console.log(err);
+      }
+    };
+
+    const close = () => {
+      data.message = {};
+    };
+
+    const handleDialogPosition = () => {
+      data.isMenuOpen = !!document?.getElementsByClassName('home-menu')?.length;
+    };
+
+    return {
+      ...toRefs(data),
+      dialog,
+      toggleShow,
+      close,
+      caculateTimeago,
+      handleDialogPosition,
+    };
+  },
+});
+export default ReadReceiptDialog;
+</script>
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 143 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/style/h5.scss

@@ -0,0 +1,143 @@
+.replies-H5 {
+  z-index: 5;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  transform: none;
+  box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+  background: #ffffff;
+  border-radius: 8Px;
+  overflow: hidden;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  padding: 0;
+
+  .header {
+    padding-left: 20Px;
+    padding-right: 20Px;
+
+    &-title {
+      text-align: center;
+    }
+  }
+
+  .body {
+    padding: 0;
+
+    .body-tab,
+    .body-list {
+      width: 100%;
+    }
+
+    .body-list-item {
+      background-color: #ffffff;
+      padding-left: 10Px;
+      display: flex;
+      flex-direction: row;
+
+      .avatar {
+        border-radius: 10%;
+        padding: 0;
+        margin: 10Px;
+      }
+
+      .name {
+        width: calc(100% - 40Px);
+        height: 56Px;
+        line-height: 56Px;
+        border-bottom: 1Px solid #efefef;
+      }
+    }
+
+    .body-tab {
+      height: 22Px;
+      padding: 15Px;
+      width: calc(100% - 30Px);
+      border-bottom: none;
+
+      .tab-item {
+        display: flex;
+        flex-direction: row-reverse;
+        justify-content: center;
+        align-items: center;
+        font-size: 16Px;
+        height: 22Px;
+        font-family: PingFangSC-Medium;
+
+        &-title,
+        &-count {
+          height: 22Px;
+          font-size: 15Px;
+          letter-spacing: 0;
+          text-align: center;
+          line-height: 20Px;
+          font-weight: 400;
+          color: #8d8d8d;
+        }
+
+        &-count {
+          font-size: 17Px;
+          font-weight: 800;
+        }
+
+        &-now {
+          color: #147aff;
+          padding-bottom: 3Px;
+          border-bottom: 2.6Px solid #147aff;
+
+          .tab-item-title,
+          .tab-item-count {
+            color: #147aff;
+          }
+        }
+      }
+    }
+
+    .body-message {
+      .message {
+        margin: 10Px 0;
+        background-color: #ffffff;
+        padding: 5Px;
+
+        .message-info {
+          span {
+            padding-left: 10Px;
+            padding-right: 10Px;
+            width: 94Px;
+            height: 30Px;
+            font-family: PingFangSC-Regular;
+            font-weight: 400;
+            font-size: 12Px;
+            color: #999999;
+            letter-spacing: 0;
+            text-align: center;
+            line-height: 30Px;
+          }
+        }
+
+        .message-cont {
+          padding: 0 10Px 10Px 10Px;
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          font-size: 14Px;
+          color: #111111;
+          letter-spacing: 0;
+          max-height: 100Px;
+
+          p {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+
+          &-img {
+            max-height: 70Px;
+          }
+        }
+      }
+    }
+  }
+}

+ 4 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/style/index.scss

@@ -0,0 +1,4 @@
+@import './web.scss';
+@import './h5.scss';
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');

+ 122 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/replies/style/web.scss

@@ -0,0 +1,122 @@
+.replies {
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  width: 400Px;
+  overflow-y: auto;
+  border-radius: 8Px 0 0 8Px;
+  position: absolute;
+  right: 0;
+  height: calc(100% - 40Px);
+  z-index: 2;
+  top: 40Px;
+  background: #ffffff;
+  box-shadow: 0 1Px 10Px 0 rgb(2 16 43 / 15%);
+
+  .header {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    height: 30Px;
+    padding: 10Px 0;
+
+    &-title {
+      width: calc(100% - 40Px);
+      text-align: left;
+      height: 30Px;
+      font-family: PingFangSC-Medium;
+      font-weight: 500;
+      font-size: 16Px;
+      color: #333333;
+      line-height: 30Px;
+    }
+
+    &-close {
+      width: 20Px;
+
+      i {
+        width: 12Px;
+        height: 12Px;
+      }
+    }
+
+    &-back {
+      width: 20Px;
+    }
+  }
+
+  .body {
+    padding: 10Px 0;
+    width: 100%;
+    flex: 1 1 auto;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    .body-message {
+      border-bottom: 1Px dashed #e0e0e0;
+    }
+
+    .body-list {
+      width: 100%;
+      flex: 1 1 auto;
+      overflow-y: auto;
+
+      &-item {
+        display: flex;
+        flex: 1 1 auto;
+        flex-direction: row;
+        flex-wrap: nowrap;
+        align-items: center;
+
+        .replies-item {
+          flex: 1;
+        }
+
+        .avatar {
+          width: 36Px;
+          height: 36Px;
+          margin: 10Px 10Px 10Px 0Px;
+          border-radius: 10%;
+        }
+
+        .name {
+          width: calc(100% - 60Px);
+          height: 20Px;
+          display: inline-block;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          line-height: 20Px;
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          font-size: 14Px;
+          color: #000000;
+          letter-spacing: 0;
+        }
+      }
+
+      .more {
+        text-align: center;
+        color: rgba(0, 0, 0, 0.3);
+        font-size: 12Px;
+        padding: 10Px;
+      }
+    }
+  }
+}
+
+::-webkit-scrollbar {
+  width: 4Px;
+  height: 140Px;
+  background-color: transparent;
+}
+
+::-webkit-scrollbar-track {
+  border-radius: 10Px;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 10Px;
+  background-color: #d8d8d8;
+}

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/typingHeader/index.ts

@@ -0,0 +1,3 @@
+import TypingHeader from './typingHeader.vue';
+
+export default TypingHeader;

+ 210 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/typingHeader/typingHeader.vue

@@ -0,0 +1,210 @@
+<template>
+  <h1>{{ title === '对方正在输入' ? $t('TUIChat.对方正在输入') : title }}</h1>
+</template>
+
+<script lang="ts">
+import { defineComponent, watchEffect, watch, reactive, toRefs, computed, nextTick } from 'vue';
+import { handleName, JSONToObject, isTypingMessage } from '../../utils/utils';
+import constant from '../../../constant';
+const TypingHeader = defineComponent({
+  props: {
+    needTyping: {
+      type: Boolean,
+      default: false,
+    },
+    conversation: {
+      type: Object,
+      default: () => ({}),
+    },
+    messageList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  setup(props: any, ctx: any) {
+    const data = reactive({
+      messageList: [],
+      conversation: {},
+      title: '',
+      timeValid: 0,
+      myTypingStatus: 0,
+      otherTypingStatus: 0,
+      needTyping: false,
+      lastOtherMessageTime: 0,
+      lastMyTypingTime: 0,
+      lastOtherTypingTime: 0,
+      options: {
+        data: {
+          businessID: constant.typeUserTyping,
+          typingStatus: 0,
+          version: 1,
+          userAction: 0,
+          actionParam: constant.typeInputStatusEnd,
+        },
+        description: '',
+        extension: '',
+      },
+    });
+
+    watchEffect(() => {
+      data.messageList = props.messageList;
+      data.conversation = props.conversation;
+      data.needTyping = props.needTyping;
+    });
+    const conversationID = computed(() => {
+      const { conversation }: any = data;
+      return conversation?.conversationID ? conversation.conversationID : '';
+    });
+    const conversationName = computed(() => {
+      const { conversation }: any = data;
+      return handleName(conversation);
+    });
+    const conversationType = computed(() => {
+      const { conversation }: any = data;
+      return conversation?.type ? conversation?.type : '';
+    });
+
+    const title = computed(() => {
+      if (data.needTyping && data.otherTypingStatus) {
+        return '对方正在输入';
+      }
+      return conversationName?.value;
+    });
+
+
+    const onTyping = (inputContentEmpty: boolean, inputBlur: boolean) => {
+      if (!data.needTyping || conversationType.value !== 'C2C') return;
+      if (new Date().getTime() / 1000 - data.lastOtherMessageTime < 30) {
+        data.timeValid = 1;
+      }
+      if (!data.timeValid) return;
+      if (!inputContentEmpty && !inputBlur) {
+        data.myTypingStatus = 1;
+        const now = new Date().getTime();
+        if (now - data.lastMyTypingTime > 4000) {
+          data.lastMyTypingTime = now;
+          sendTypingMessage(data.myTypingStatus);
+        }
+      } else {
+        data.myTypingStatus = 0;
+        data.lastMyTypingTime = 0;
+        sendTypingMessage(data.myTypingStatus);
+      }
+    };
+
+    watch(conversationID, (newVal: any, oldVal: any) => {
+      if (newVal === oldVal) return;
+      data.needTyping = false;
+      data.timeValid = 0;
+    });
+
+    watch(
+      () => data.needTyping,
+      (newVal: any, oldVal: any) => {
+        if (!newVal) {
+          data.myTypingStatus = 0;
+          data.otherTypingStatus = 0;
+          data.lastOtherMessageTime = 0;
+        }
+      }
+    );
+
+    watch(
+      () => data.messageList,
+      (newVal: any, oldVal: any) => {
+        nextTick(() => {
+          if (newVal.length === 0 || conversationType.value !== 'C2C') {
+            return;
+          }
+          data.lastOtherMessageTime = getLastOtherMessageTime(newVal);
+          if (newVal[newVal.length - 1]?.flow === 'in') {
+            if (!isTypingMessage(newVal[newVal.length - 1])) {
+              data.lastOtherMessageTime = newVal[newVal.length - 1]?.time;
+              data.otherTypingStatus = 0;
+            } else {
+              data.otherTypingStatus = handleTypingMessageStatus(newVal[newVal.length - 1]);
+              waitTypingEnd();
+            }
+          }
+        });
+      },
+      { deep: true }
+    );
+
+    const handleTypingMessageStatus = (item: any) => {
+      try {
+        const { typingStatus, actionParam }: any = JSONToObject(item?.payload?.data);
+        if (typingStatus === 1 && actionParam === constant.typeInputStatusIng) {
+          return 1;
+        }
+        return 0;
+      } catch {
+        return 0;
+      }
+    };
+
+    const sendTypingMessage = (isTyping: any) => {
+      data.options = {
+        data: {
+          businessID: constant.typeUserTyping,
+          typingStatus: isTyping ? 1 : 0,
+          version: 1,
+          userAction: isTyping ? 14 : 0,
+          actionParam: isTyping ? constant.typeInputStatusIng : constant.typeInputStatusEnd,
+        },
+        description: '',
+        extension: '',
+      };
+      TypingHeader.TUIServer.sendTypingMessage(data.options);
+      return;
+    };
+
+    const getLastOtherMessageTime = (messageList: any) => {
+      if (!messageList) return 0;
+      for (let i = messageList.length - 1; i >= 0; i--) {
+        if (!isTypingMessage(messageList[i]) && messageList[i].flow === 'in') {
+          return messageList[i].time;
+        }
+      }
+      return 0;
+    };
+
+    const debounce = (func: any, wait = 2000) => {
+      let timer: any;
+      return function () {
+        if (timer) clearTimeout(timer);
+        timer = setTimeout(func, wait);
+      };
+    };
+
+    const waitTypingEnd = debounce(() => {
+      data.otherTypingStatus = 0;
+    }, 5000);
+
+    return {
+      ...toRefs(data),
+      conversationID,
+      conversationName,
+      conversationType,
+      title,
+      isTypingMessage,
+      sendTypingMessage,
+      handleTypingMessageStatus,
+      getLastOtherMessageTime,
+      debounce,
+      waitTypingEnd,
+      onTyping,
+    };
+  },
+});
+export default TypingHeader;
+</script>
+<style scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+h1 {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/video/index.ts

@@ -0,0 +1,3 @@
+import Video from './video.vue';
+
+export default Video;

+ 72 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/video/video.vue

@@ -0,0 +1,72 @@
+<template>
+  <span class="upload-btn icon icon-video">
+      <input title="视频" v-if="!isMute" type="file" data-type="video" accept="video/*" @change="sendUploadMessage" />
+      <slot />
+  </span>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs, watchEffect } from 'vue';
+import { handleErrorPrompts } from '../../../utils';
+
+
+const Video = defineComponent({
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false,
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  setup(props:any, ctx:any) {
+    const data = reactive({
+      isMute: false,
+    });
+
+    watchEffect(() => {
+      data.isMute = props.isMute;
+    });
+    // 发送需要上传的消息:视频
+    const sendUploadMessage = async (e:any) => {
+      if (e.target.files.length > 0) {
+        try {
+          await Video.TUIServer.sendVideoMessage(e.target);
+        } catch (error) {
+          handleErrorPrompts(error, props);
+        }
+      }
+      e.target.value = '';
+    };
+
+    return {
+      ...toRefs(data),
+      sendUploadMessage,
+    };
+  },
+});
+export default Video;
+</script>
+
+<style lang="scss" scoped>
+@import url('../../../../styles/common.scss');
+@import url('../../../../styles/icon.scss');
+.upload-btn {
+  position: relative;
+  input {
+    position: absolute;
+    cursor: pointer;
+    left: 0;
+    top: 0;
+    opacity: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 3 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/index.ts

@@ -0,0 +1,3 @@
+import Words from './words.vue';
+
+export default Words;

+ 59 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/color.scss

@@ -0,0 +1,59 @@
+.words {
+  &-main {
+    background: #ffffff;
+    box-shadow: 0 2Px 12Px 0 rgba(0, 0, 0, 0.1);
+
+    header {
+      aside {
+        font-weight: 500;
+        color: #000000;
+
+        h1 {
+          font-weight: 500;
+          color: #000000;
+        }
+
+        a {
+          color: #006eff;
+        }
+      }
+    }
+  }
+
+  &-list {
+    &-item {
+      cursor: pointer;
+      font-weight: 400;
+      color: #50545c;
+
+      &:hover {
+        color: #006eff;
+      }
+    }
+  }
+}
+
+.words-H5-main {
+  background: rgba(0, 0, 0, 0.5);
+
+  .words-main-content {
+    background: #ffffff;
+
+    .words-list {
+      &-item {
+        border-bottom: 1Px solid #eeeeee;
+        font-family: PingFangSC-Regular;
+        font-weight: 400;
+        color: #333333;
+        letter-spacing: 0;
+      }
+    }
+
+    .close {
+      font-family: PingFangSC-Regular;
+      font-weight: 400;
+      color: #3370ff;
+      letter-spacing: 0;
+    }
+  }
+}

+ 52 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/h5.scss

@@ -0,0 +1,52 @@
+.words-H5 {
+  display: inline-block;
+  position: relative;
+  cursor: auto;
+
+  .words-H5-main {
+    position: fixed;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 100vw;
+    height: auto;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    padding: 0;
+
+    .words-main-content {
+      padding: 23Px 27Px 12Px;
+      border-radius: 12Px 12Px 0 0;
+
+      header {
+        padding: 0;
+
+        aside {
+          flex-direction: column;
+          align-items: flex-start;
+          font-size: 16Px;
+          line-height: 28Px;
+
+          h1 {
+            font-size: 20Px;
+          }
+        }
+
+        .close {
+          font-size: 18Px;
+          line-height: 27Px;
+        }
+      }
+
+      .words-list {
+        &-item {
+          padding: 12Px 0;
+          font-size: 18Px;
+          line-height: 25Px;
+        }
+      }
+    }
+  }
+}

+ 5 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/index.scss

@@ -0,0 +1,5 @@
+@import './color.scss';
+@import './web.scss';
+@import './h5.scss';
+@import url('../../../../../styles/common.scss');
+@import url('../../../../../styles/icon.scss');

+ 49 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/style/web.scss

@@ -0,0 +1,49 @@
+.words {
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+
+  .icon {
+    margin: 12Px 10Px 0;
+  }
+
+  &-main {
+    position: absolute;
+    z-index: 5;
+    width: 315Px;
+    top: -200Px;
+    padding: 12Px;
+    display: flex;
+    flex-direction: column;
+    width: 19.13rem;
+    height: 12.44rem;
+    overflow-y: auto;
+
+    header {
+      display: flex;
+      justify-content: space-between;
+
+      aside {
+        display: flex;
+        align-items: center;
+        font-size: 12Px;
+
+        h1 {
+          font-size: 12Px;
+        }
+      }
+    }
+  }
+
+  &-list {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+
+    &-item {
+      cursor: pointer;
+      padding: 4Px 0;
+      font-size: 12Px;
+    }
+  }
+}

+ 122 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/plugin-components/words/words.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="words" :class="[isH5 ? 'words-H5' : '']">
+      <i class="icon icon-words" title="快速回复" @click="toggleShow"></i>
+      <main class="words-main" :class="[isH5 ? 'words-H5-main' : '']" v-show="show&&!isMute">
+        <div class="words-main-content" ref="dialog">
+          <header>
+            <aside>
+              <h1>{{$t('Words.常用语-快捷回复工具')}}</h1>
+            </aside>
+            <span v-if="isH5" class="close" @click="toggleShow">关闭</span>
+          </header>
+          <ul class="words-list">
+            <li class="words-list-item" v-for="(item, index) in list" :key="index"  @click="select(item)">
+              <label>{{item.value}}</label>
+            </li>
+          </ul>
+        </div>
+      </main>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, watchEffect, toRefs, watch, ref } from 'vue';
+import { useI18nLocale  } from '../../../../../TUIPlugin/TUIi18n';
+import { onClickOutside } from '@vueuse/core';
+
+const Words = defineComponent({
+  type: 'custom',
+  props: {
+    show: {
+      type: Boolean,
+      default: () => false,
+    },
+    isMute: {
+      type: Boolean,
+      default: () => false,
+    },
+    isH5: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  setup(props:any, ctx:any) {
+    const { t } = (window as any).TUIKitTUICore.config.i18n.useI18n();
+    const list:any = [
+      {
+        value: '在吗?在吗?在吗?重要的话说三遍。',
+      },
+      {
+        value: '好久没聊天了,快来和我说说话~',
+      },
+      {
+        value: '好的,就这么说定了。',
+      },
+      {
+        value: '感恩的心,感谢有你。',
+      },
+      {
+        value: '糟糕!是心动的感觉!',
+      },
+      {
+        value: '心疼地抱抱自己,我太难了!',
+      },
+      {
+        value: '没关系,别在意,事情过去就过去了。',
+      },
+      {
+        value: '早上好,今天也是让人期待的一天呢!',
+      },
+      {
+        value: '熬夜有什么用,又没人陪你聊天,早点休息吧。',
+      },
+    ];
+    const data = reactive({
+      list: [],
+      show: false,
+      isMute: false,
+      locale: useI18nLocale(),
+    });
+
+    const dialog:any = ref();
+
+    watch(() => data.locale, (newVal:any, oldVal:any) => {
+      data.list = list.map((item:any) => ({
+        value: t(`Words.${item.value}`),
+      }));
+    });
+
+    watchEffect(() => {
+      data.show = props.show;
+      data.isMute = props.isMute;
+    });
+
+    const toggleShow = () => {
+      data.show = !data.show;
+      if (data.show) {
+        data.list = list.map((item:any) => ({
+          value: t(`Words.${item.value}`),
+        }));
+      }
+    };
+
+    onClickOutside(dialog, () => {
+      data.show = false;
+    });
+
+    const select = (item:any, index?:number) => {
+      const text = item.value;
+      toggleShow();
+      Words.TUIServer.sendTextMessage(text);
+    };
+    return {
+      ...toRefs(data),
+      toggleShow,
+      select,
+      dialog,
+    };
+  },
+});
+export default Words;
+</script>
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 1354 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/server.ts

@@ -0,0 +1,1354 @@
+import { TUINotification } from '../../../TUIPlugin';
+import Message from '../../../TUICore/tim/index';
+import IComponentServer from '../IComponentServer';
+import { JSONToObject } from './utils/utils';
+/**
+ * class TUIChatServer
+ *
+ * TUIChat 逻辑主体
+ */
+export default class TUIChatServer extends IComponentServer {
+  public TUICore: any;
+  public store: any;
+  public currentStore: any = {};
+  constructor(TUICore: any) {
+    super();
+    this.TUICore = TUICore;
+    this.bindTIMEvent();
+    this.store = TUICore.setComponentStore(
+      'TUIChat',
+      {},
+      this.updateStore.bind(this)
+    );
+  }
+
+  /**
+   * 组件销毁
+   * destroy
+   */
+  public destroyed() {
+    this.unbindTIMEvent();
+  }
+
+  /**
+   * 数据监听回调
+   * data listener callback
+   *
+   * @param {any} newValue 新数据
+   * @param {any} oldValue 旧数据
+   *
+   */
+  updateStore(newValue: any, oldValue: any) {
+    Object.assign(this.currentStore, newValue);
+    if (!newValue.conversation.conversationID) {
+      this.currentStore.messageList = [];
+      return;
+    }
+    if (
+      newValue.conversation.conversationID &&
+      newValue.conversation.conversationID !==
+        oldValue.conversation.conversationID
+    ) {
+      this.render(newValue.conversation);
+    }
+  }
+
+  public render(conversation: any) {
+    const len = 15;
+    this.currentStore.isFirstRender = true;
+    this.currentStore.messageList = [];
+    this.currentStore.readSet.clear();
+    this.getMessageList({
+      conversationID: conversation.conversationID,
+      count: len
+    });
+    if (conversation.type === this.TUICore.TIM.TYPES.CONV_GROUP) {
+      this.currentStore.userInfo.isGroup = true;
+      const options = {
+        groupID: conversation.groupProfile.groupID,
+        userIDList: [conversation.groupProfile.selfInfo.userID]
+      };
+      this.getGroupProfile({ groupID: conversation.groupProfile.groupID });
+      this.getGroupMemberProfile(options).then((res: any) => {
+        const { memberList } = res.data;
+        const [selfInfo] = memberList;
+        this.currentStore.selfInfo = selfInfo;
+      });
+      this?.TUICore?.TUIServer?.TUIGroup?.getGroupMemberList({
+        groupID: conversation.groupProfile.groupID,
+        count: 100,
+        offset: 0
+      }).then((res: any) => {
+        this.currentStore.allMemberList = res.data?.memberList;
+      });
+    } else {
+      this.currentStore.userInfo.isGroup = false;
+      this.currentStore.userInfo.list = [conversation?.userProfile];
+    }
+  }
+
+  /**
+   * /////////////////////////////////////////////////////////////////////////////////
+   * //
+   * //                                    TIM 事件监听注册接口
+   * //                        TIM Event listener registration interface
+   * //
+   * /////////////////////////////////////////////////////////////////////////////////
+   */
+
+  private bindTIMEvent() {
+    this.TUICore.tim.on(
+      this.TUICore.TIM.EVENT.MESSAGE_RECEIVED,
+      this.handleMessageReceived,
+      this
+    );
+    this.TUICore.tim.on(
+      this.TUICore.TIM.EVENT.MESSAGE_MODIFIED,
+      this.handleMessageModified,
+      this
+    );
+    this.TUICore.tim.on(
+      this.TUICore.TIM.EVENT.MESSAGE_REVOKED,
+      this.handleMessageRevoked,
+      this
+    );
+    this.TUICore.tim.on(
+      this.TUICore.TIM.EVENT.MESSAGE_READ_BY_PEER,
+      this.handleMessageReadByPeer,
+      this
+    );
+    this.TUICore.tim.on(
+      this.TUICore.TIM.EVENT.GROUP_LIST_UPDATED,
+      this.handleGroupListUpdated,
+      this
+    );
+    this.TUICore.tim.on(
+      this.TUICore.TIM.EVENT.MESSAGE_READ_RECEIPT_RECEIVED,
+      this.handleMessageReadReceiptReceived,
+      this
+    );
+  }
+
+  private unbindTIMEvent() {
+    this.TUICore.tim.off(
+      this.TUICore.TIM.EVENT.MESSAGE_RECEIVED,
+      this.handleMessageReceived
+    );
+    this.TUICore.tim.off(
+      this.TUICore.TIM.EVENT.MESSAGE_MODIFIED,
+      this.handleMessageModified
+    );
+    this.TUICore.tim.off(
+      this.TUICore.TIM.EVENT.MESSAGE_REVOKED,
+      this.handleMessageRevoked
+    );
+    this.TUICore.tim.off(
+      this.TUICore.TIM.EVENT.MESSAGE_READ_BY_PEER,
+      this.handleMessageReadByPeer
+    );
+    this.TUICore.tim.off(
+      this.TUICore.TIM.EVENT.GROUP_LIST_UPDATED,
+      this.handleGroupListUpdated
+    );
+    this.TUICore.tim.off(
+      this.TUICore.TIM.EVENT.MESSAGE_READ_RECEIPT_RECEIVED,
+      this.handleMessageReadReceiptReceived
+    );
+  }
+
+  private handleMessageReceived(event: any) {
+    event?.data?.forEach((message: Message) => {
+      if (
+        message?.conversationID === this?.store?.conversation?.conversationID
+      ) {
+        this.currentStore.messageList = [
+          ...this.currentStore.messageList,
+          message
+        ];
+      }
+      TUINotification.getInstance().notify(message);
+    });
+  }
+
+  private handleMessageModified(event: any) {
+    const middleData = this.currentStore.messageList;
+    this.currentStore.messageList = [];
+    this.currentStore.messageList = middleData;
+  }
+  private handleMessageRevoked(event: any) {
+    const middleData = this.currentStore.messageList;
+    this.currentStore.messageList = [];
+    this.currentStore.messageList = middleData;
+  }
+  private handleMessageReadByPeer(event: any) {
+    const middleData = this.currentStore.messageList;
+    this.currentStore.messageList = [];
+    this.currentStore.messageList = middleData;
+  }
+
+  private handleGroupListUpdated(event: any) {
+    event?.data.map((item: any) => {
+      if (item?.groupID === this?.store?.conversation?.groupProfile?.groupID) {
+        this.store.conversation.groupProfile = item;
+        this.currentStore.conversation = {};
+        this.currentStore.conversation = this.store.conversation;
+      }
+      return item;
+    });
+  }
+
+  private handleMessageReadReceiptReceived(event: any) {
+    const middleData = this.currentStore.messageList;
+    this.currentStore.messageList = [];
+    this.currentStore.messageList = middleData;
+  }
+
+  /**
+   * /////////////////////////////////////////////////////////////////////////////////
+   * //
+   * //                                 处理 TIM 接口参数及回调
+   * //                     Handling TIM interface parameters and callbacks
+   * //
+   * /////////////////////////////////////////////////////////////////////////////////
+   */
+
+  /**
+   * 创建消息生成参数
+   * Create message generation parameters
+   *
+   * @param {Object} content 消息体
+   * @param {String} type 消息类型 text: 文本类型 file: 文件类型 face: 表情 location: 地址 custom: 自定义 merger: 合并 forward: 转发
+   * @param {Callback} callback 回调函数
+   * @param {any} to 发送的对象
+   * @returns {options} 消息参数
+   */
+  public handleMessageOptions(
+    content: any,
+    type: string,
+    callback?: any,
+    to?: any
+  ) {
+    const options: any = {
+      to: '',
+      conversationType: to?.type || this.store.conversation.type,
+      payload: content,
+      needReadReceipt: this.currentStore.needReadReceipt
+    };
+    if (this.currentStore.needTyping) {
+      options.cloudCustomData = {
+        messageFeature: {
+          needTyping: 1,
+          version: 1
+        }
+      };
+      options.cloudCustomData = JSON.stringify(options.cloudCustomData);
+    }
+    if (type === 'file' && callback) {
+      options.onProgress = callback;
+    }
+    switch (options.conversationType) {
+      case this.TUICore.TIM.TYPES.CONV_C2C:
+        options.to =
+          to?.userProfile?.userID ||
+          this.store.conversation?.userProfile?.userID ||
+          '';
+        break;
+      case this.TUICore.TIM.TYPES.CONV_GROUP:
+        options.to =
+          to?.groupProfile?.groupID ||
+          this.store.conversation?.groupProfile?.groupID ||
+          '';
+        break;
+      default:
+        break;
+    }
+    return options;
+  }
+
+  /**
+   * 处理异步函数
+   * Handling asynchronous functions
+   *
+   * @param {callback} callback 回调函数
+   * @returns {Promise} 返回异步函数
+   */
+  public handlePromiseCallback(callback: any) {
+    return new Promise<void>((resolve, reject) => {
+      const config = {
+        TUIName: 'TUIChat',
+        callback: () => {
+          callback && callback(resolve, reject);
+        }
+      };
+      this.TUICore.setAwaitFunc(config.TUIName, config.callback);
+    });
+  }
+
+  /**
+   * 重试异步函数
+   * Retry asynchronous functions
+   * 默认执行一次,之后按时间间隔列表重复执行直到成功,重复次数完毕后仍失败则失败
+   * Execute once by default, and then repeat it according to the time interval list until it succeeds.
+   * If it still fails after the number of repetitions is complete, it will reject.
+   *
+   * @param {callback} callback 回调函数/callback function
+   * @param {Array<number>} intervalList 间隔时间列表/interval list
+   * @param {callback} retryBreakFn 强制重复结束条件函数/break retry function
+   * @returns {Promise} 返回异步函数/return
+   */
+  public handlePromiseCallbackRetry(
+    callback: any,
+    intervalList: Array<number> = [],
+    retryBreakFn: any = function () {
+      return false;
+    }
+  ): Promise<any> {
+    return new Promise<void>((resolve, reject) => {
+      let times = 0;
+      function tryFn() {
+        times++;
+        callback()
+          .then(resolve)
+          .catch((error: any) => {
+            if (
+              times > intervalList.length ||
+              (retryBreakFn && retryBreakFn(error))
+            ) {
+              reject(error);
+              return;
+            }
+            setTimeout(tryFn, intervalList[times - 1]);
+          });
+      }
+      tryFn();
+    });
+  }
+
+  /**
+   * 文件上传进度函数处理
+   * File upload progress function processing
+   *
+   * @param {number} progress 文件上传进度 1表示完成
+   * @param {message} message 文件消息
+   */
+  public handleUploadProgress(progress: number, message: any) {
+    this.currentStore.messageList.map((item: any) => {
+      if (item.ID === message.ID) {
+        item.progress = progress;
+      }
+      return item;
+    });
+  }
+
+  /**
+   * /////////////////////////////////////////////////////////////////////////////////
+   * //
+   * //                                 TIM 方法
+   * //                               TIM methods
+   * //
+   * /////////////////////////////////////////////////////////////////////////////////
+   */
+
+  /**
+   * 发送表情消息
+   * Send face messages
+   *
+   * @param {Object} data 消息内容/message content
+   * @param {Number} data.index 表情索引/face index
+   * @param {String} data.data 额外数据/extra data
+   * @returns {Promise}
+   */
+  public sendFaceMessage(data: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(data, 'face');
+        const message = this.TUICore.tim.createFaceMessage(options);
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送图片消息
+   * Send image message
+   *
+   * @param {Image} image 图片文件/image
+   * @returns {Promise}
+   */
+  public sendImageMessage(image: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(
+          { file: image },
+          'file',
+          (progress: number) => {
+            this.handleUploadProgress(progress, message);
+          }
+        );
+        const message = this.TUICore.tim.createImageMessage(options);
+        message.progress = 0.01;
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送视频消息
+   * Send video message
+   *
+   * @param {Video} video 视频文件/video
+   * @returns {Promise}
+   */
+  public sendVideoMessage(video: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(
+          { file: video },
+          'file',
+          (progress: number) => {
+            this.handleUploadProgress(progress, message);
+          }
+        );
+        const message = this.TUICore.tim.createVideoMessage(options);
+        message.progress = 0.01;
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送文件消息
+   * Send file message
+   *
+   * @param {File} file 文件/file
+   * @returns {Promise}
+   */
+  public sendFileMessage(file: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(
+          { file },
+          'file',
+          (progress: number) => {
+            this.handleUploadProgress(progress, message);
+          }
+        );
+        const message = this.TUICore.tim.createFileMessage(options);
+        message.progress = 0.01;
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送自定义消息
+   * Send Custom message
+   *
+   * @param {Object} data 消息内容/message content
+   * @param {String} data.data 自定义消息的数据字段/custom message data fields
+   * @param {String} data.description 自定义消息的说明字段/custom message description fields
+   * @param {String} data.extension 自定义消息的扩展字段/custom message extension fields
+   * @returns {Promise}
+   */
+  public sendCustomMessage(data: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        data.data = JSON.stringify(data.data);
+        const options = this.handleMessageOptions(data, 'custom');
+        const message = this.TUICore.tim.createCustomMessage(options);
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送地理位置消息
+   * Send location message
+   *
+   * @param {Object} data 消息内容/message content
+   * @param {String} data.description 地理位置描述信息/geographic descriptive information
+   * @param {Number} data.longitude 经度/longitude
+   * @param {Number} data.latitude 纬度/latitude
+   * @returns {Promise}
+   */
+  public sendLocationMessage(data: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(data, 'location');
+        const message = this.TUICore.tim.createLocationMessage(options);
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 转发消息
+   * forward message
+   *
+   * @param {message} message 消息实例/message
+   * @param {any} to 转发的对象/forward to
+   * @returns {Promise}
+   */
+  public forwardMessage(message: any, to: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(message, 'forward', {}, to);
+        const imMessage = this.TUICore.tim.createForwardMessage(options);
+        const imResponse = await this.TUICore.tim.sendMessage(imMessage);
+        if (
+          this.store.conversation.conversationID ===
+          imResponse.data.message.conversationID
+        ) {
+          this.currentStore.messageList.push(imResponse.data.message);
+        }
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送消息已读回执
+   * Send message read receipt
+   *
+   * @param {Array} messageList 同一个 C2C 或 GROUP 会话的消息列表,最大长度为30/A list of messages for the same C2C or GROUP conversation, with a maximum length of 30
+   * @returns {Promise}
+   */
+  public async sendMessageReadReceipt(messageList: Array<any>) {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse: any = await this.TUICore.tim.sendMessageReadReceipt(
+          messageList
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 拉取已读回执列表
+   * Pull read receipt list
+   *
+   * @param {Array} messageList 同一群会话的消息列表/The message list of the same group of the conversation
+   * @returns {Promise}
+   */
+  public async getMessageReadReceiptList(messageList: Array<any>) {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse: any =
+          await this.TUICore.tim.getMessageReadReceiptList(messageList);
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * /////////////////////////////////////////////////////////////////////////////////
+   * //
+   * //                                 对外方法
+   * //
+   * /////////////////////////////////////////////////////////////////////////////////
+   */
+
+  /**
+   * 获取 messageList
+   * get messagelist
+   *
+   * @param {any} options 获取 messageList 参数/messageList options
+   * @param {Boolean} history  是否获取历史消息/Whether to get historical information
+   * @returns {Promise}
+   */
+  public async getMessageList(options: any, history?: boolean) {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getMessageList(options);
+        if (imResponse.data.messageList.length) {
+          await this.getMessageReadReceiptList(imResponse.data.messageList);
+        }
+        if (!history) {
+          this.currentStore.messageList = imResponse.data.messageList;
+        } else {
+          this.currentStore.messageList = [
+            ...imResponse.data.messageList,
+            ...this.currentStore.messageList
+          ];
+        }
+        this.currentStore.nextReqMessageID = imResponse.data.nextReqMessageID;
+        this.currentStore.isCompleted = imResponse.data.isCompleted;
+
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取历史消息
+   * get history messagelist
+   *
+   * @returns {Promise}
+   */
+  public async getHistoryMessageList() {
+    const options = {
+      conversationID: this.currentStore.conversation.conversationID,
+      nextReqMessageID: this.currentStore.nextReqMessageID,
+      count: 15
+    };
+    if (!this.currentStore.isCompleted) {
+      this.getMessageList(options, true);
+    }
+  }
+
+  /**
+   * 发送文本消息
+   * send text message
+   *
+   * @param {any} text 发送的消息/text message
+   * @param {object} data 被引用消息的内容/The content of the quoted message
+   * @returns {Promise}
+   */
+  public sendTextMessage(text: any, data: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions({ text }, 'text');
+        let cloudCustomDataObj = {};
+        if (options.cloudCustomData) {
+          try {
+            cloudCustomDataObj = JSONToObject(options.cloudCustomData);
+          } catch {
+            cloudCustomDataObj = {};
+          }
+        }
+        const cloudCustomData = JSON.stringify(data);
+        const secondOptions = Object.assign(options, {
+          cloudCustomData,
+          ...cloudCustomDataObj
+        });
+        const message = this.TUICore.tim.createTextMessage(secondOptions);
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送【对方正在输入中】在线自定义消息
+   * send typing online custom message
+   *
+   * @param {Object} data 消息内容/message content
+   * @param {String} data.data 自定义消息的数据字段/custom message data field
+   * @param {String} data.description 自定义消息的说明字段/custom message description field
+   * @param {String} data.extension 自定义消息的扩展字段/custom message extension field
+   * @returns {Promise}
+   */
+  public sendTypingMessage(data: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        data.data = JSON.stringify(data.data);
+        const options = this.handleMessageOptions(data, 'custom');
+        const message = this.TUICore.tim.createCustomMessage(options);
+        const imResponse = await this.TUICore.tim.sendMessage(message, {
+          onlineUserOnly: true
+        });
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送@ 提醒功能的文本消息
+   * Send @ Reminder text message
+   *
+   * @param {any} data 消息内容/message content
+   * @param {String} data.text 文本消息/text message
+   * @param {Array} data.atUserList 需要 @ 的用户列表,如果需要 @ALL,请传入 TIM.TYPES.MSG_AT_ALL / List of users who need @, if you need @ALL, please pass in TIM.TYPES.MSG_AT_ALL
+   * @returns {message}
+   *
+   * - 注:此接口仅用于群聊/This interface is only used for group chat
+   */
+  public sendTextAtMessage(data: any) {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(data, 'text');
+        const message = this.TUICore.tim.createTextAtMessage(options);
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 发送合并消息
+   * send merger message
+   *
+   * @param {Object} data 消息内容/message content
+   * @param {Array.<Message>} data.messageList 合并的消息列表/merger message list
+   * @param {String} data.title 合并的标题/merger title
+   * @param {String} data.abstractList 摘要列表,不同的消息类型可以设置不同的摘要信息/Summary list, different message types can set different summary information
+   * @param {String} data.compatibleText 兼容文本/ompatible text
+   * @returns {Promise}
+   */
+  public sendMergerMessage(data: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const options = this.handleMessageOptions(data, 'merger');
+        const message = this.TUICore.tim.createMergerMessage(options);
+        this.currentStore.messageList.push(message);
+        const imResponse = await this.TUICore.tim.sendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.map(
+          (item: any) => {
+            if (item.ID === imResponse.data.message.ID) {
+              return imResponse.data.message;
+            }
+            return item;
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 消息撤回
+   * revoke message
+   *
+   * @param {message} message 消息实例/message
+   * @returns {Promise}
+   */
+  public revokeMessage(message: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.revokeMessage(message);
+        const cloudCustomData = JSONToObject(message?.cloudCustomData);
+        if (cloudCustomData?.messageReply?.messageRootID) {
+          await this.revokeReplyMessage(message);
+        }
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      }
+    });
+  }
+
+  /**
+   * 重发消息
+   * resend message
+   *
+   * @param {message} message 消息实例/message
+   * @returns {Promise}
+   */
+  public resendMessage(message: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.resendMessage(message);
+        this.currentStore.messageList = this.currentStore.messageList.filter(
+          (item: any) => item.ID !== message.ID
+        );
+        this.currentStore.messageList.push(imResponse.data.message);
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 删除消息
+   * delete message
+   *
+   * @param {Array.<message>} messages 消息实例/message
+   * @returns {Promise}
+   */
+  public deleteMessage(messages: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.deleteMessage(messages);
+        resolve(imResponse);
+        const middleData = this.currentStore.messageList;
+        this.currentStore.messageList = [];
+        this.currentStore.messageList = middleData;
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 变更消息
+   * modify message
+   *
+   * @param {Array.<message>} message 消息实例/message
+   * @returns {Promise}
+   */
+  public modifyMessage(message: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.modifyMessage(message);
+        resolve(imResponse);
+      } catch (error) {
+        // 修改消息失败
+        // Modify message error
+        const code = (error as any)?.code;
+        const data = (error as any)?.data;
+        if (code === 2480) {
+          console.warn(
+            'MODIFY_MESSAGE_ERROR',
+            '修改消息发生冲突,data.message 是最新的消息',
+            'data.message:',
+            data?.message
+          );
+        } else if (code === 2481) {
+          console.warn('MODIFY_MESSAGE_ERROR', '不支持修改直播群消息');
+        } else if (code === 20026) {
+          console.warn('MODIFY_MESSAGE_ERROR', '消息不存在');
+        }
+        reject(error);
+      }
+    });
+  }
+  /**
+   * 回复消息
+   * reply message
+   * @param {Array.<message>} message 消息实例/message
+   * @returns {Promise}
+   */
+  public replyMessage(message: any, messageRoot?: any): Promise<any> {
+    const replyFunction = () => {
+      return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+        try {
+          const repliesObject = {
+            messageAbstract: message?.payload?.text,
+            messageSender: message?.from,
+            messageID: message?.ID,
+            messageType: message?.type,
+            messageTime: message?.time,
+            messageSequence: message?.sequence,
+            version: 1
+          };
+          if (!messageRoot) {
+            const cloudCustomData = JSONToObject(message?.cloudCustomData);
+            const messageRootID = cloudCustomData?.messageReply?.messageRootID;
+            messageRoot =
+              (await this?.currentStore?.messageList?.find(
+                (item: any) => item?.ID === messageRootID
+              )) || this.findMessage(messageRootID);
+          }
+          const rootCloudCustomData = messageRoot?.cloudCustomData
+            ? JSONToObject(messageRoot?.cloudCustomData)
+            : { messageReplies: {} };
+          if (rootCloudCustomData?.messageReplies?.replies) {
+            rootCloudCustomData.messageReplies.replies = [
+              // eslint-disable-next-line no-unsafe-optional-chaining
+              ...rootCloudCustomData?.messageReplies?.replies,
+              repliesObject
+            ];
+          } else {
+            rootCloudCustomData.messageReplies = {
+              replies: [repliesObject],
+              version: 1
+            };
+          }
+          messageRoot.cloudCustomData = JSON.stringify(rootCloudCustomData);
+          const imResponse = this.modifyMessage(messageRoot);
+          resolve(imResponse);
+        } catch (error) {
+          reject(error);
+        }
+      });
+    };
+    const retryBreakFunction = function (error: any) {
+      if (error && error?.code === 2480) return false;
+      return true;
+    };
+    return this.handlePromiseCallbackRetry(
+      replyFunction,
+      [500, 1000, 3000],
+      retryBreakFunction
+    );
+  }
+
+  /**
+   * 撤回回复消息
+   * revoke reply message
+   * @param {Array.<message>} message 消息实例/message
+   * @returns {Promise}
+   */
+  public revokeReplyMessage(message: any, messageRoot?: any): Promise<any> {
+    const revokeReplyFunction = () => {
+      return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+        try {
+          if (!messageRoot) {
+            const cloudCustomData = JSONToObject(message?.cloudCustomData);
+            const messageRootID = cloudCustomData?.messageReply?.messageRootID;
+            messageRoot =
+              (await this?.currentStore?.messageList?.find(
+                (item: any) => item?.ID === messageRootID
+              )) || this.findMessage(messageRootID);
+          }
+          const rootCloudCustomData = messageRoot?.cloudCustomData
+            ? JSONToObject(messageRoot?.cloudCustomData)
+            : { messageReplies: {} };
+          if (rootCloudCustomData?.messageReplies?.replies) {
+            const index = rootCloudCustomData.messageReplies.replies.findIndex(
+              (item: any) => item?.messageID === message?.ID
+            );
+            rootCloudCustomData?.messageReplies?.replies?.splice(index, 1);
+          }
+          messageRoot.cloudCustomData = JSON.stringify(rootCloudCustomData);
+          const imResponse = this.modifyMessage(messageRoot);
+          resolve(imResponse);
+        } catch (error) {
+          reject(error);
+        }
+      });
+    };
+    const retryBreakFunction = function (error: any) {
+      if (error && error?.code === 2480) return false;
+      return true;
+    };
+    return this.handlePromiseCallbackRetry(
+      revokeReplyFunction,
+      [500, 1000, 3000],
+      retryBreakFunction
+    );
+  }
+
+  /**
+   * 表情回应
+   * emoji react
+   * @param {Array.<message>} message 消息实例/message
+   * @returns {Promise}
+   */
+  public emojiReact(message: any, emojiID: any): Promise<any> {
+    const emojiReactFunction = () => {
+      return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+        try {
+          if (!message || !message?.ID || !emojiID) reject();
+          const userID =
+            this.TUICore?.TUIServer?.TUIProfile?.store?.profile?.userID;
+          message =
+            (await this?.currentStore?.messageList?.find(
+              (item: any) => item?.ID === message?.ID
+            )) || this.findMessage(message?.ID);
+          const cloudCustomData = message?.cloudCustomData
+            ? JSONToObject(message?.cloudCustomData)
+            : { messageReact: {} };
+          if (cloudCustomData?.messageReact?.reacts) {
+            if (cloudCustomData?.messageReact?.reacts[emojiID]) {
+              const index =
+                cloudCustomData?.messageReact?.reacts[emojiID]?.indexOf(userID);
+              if (index === -1) {
+                cloudCustomData?.messageReact?.reacts[emojiID]?.push(userID);
+              } else {
+                cloudCustomData?.messageReact?.reacts[emojiID]?.splice(
+                  index,
+                  1
+                );
+                if (
+                  cloudCustomData?.messageReact?.reacts[emojiID]?.length === 0
+                ) {
+                  delete cloudCustomData?.messageReact?.reacts[emojiID];
+                }
+              }
+            } else {
+              cloudCustomData.messageReact.reacts[emojiID] = [userID];
+            }
+          } else {
+            cloudCustomData.messageReact = {
+              reacts: {},
+              version: 1
+            };
+            cloudCustomData.messageReact.reacts[emojiID] = [userID];
+          }
+          message.cloudCustomData = JSON.stringify(cloudCustomData);
+          const imResponse = this.modifyMessage(message);
+          resolve(imResponse);
+        } catch (error) {
+          reject(error);
+        }
+      });
+    };
+    const retryBreakFunction = function (error: any) {
+      if (error && error?.code === 2480) return false;
+      return true;
+    };
+    return this.handlePromiseCallbackRetry(
+      emojiReactFunction,
+      [500, 1000, 3000],
+      retryBreakFunction
+    );
+  }
+
+  /**
+   * 查询消息
+   * find message
+   * @param {String} messageID 消息实例ID/messageID
+   * @returns {Promise}
+   */
+  public findMessage(messageID: string): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.findMessage(messageID);
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取群组属性
+   * get group profile
+   *
+   * @param {any} options 参数
+   * @param {String} options.groupID 群组ID
+   * @param {Array.<String>} options.groupProfileFilter 群资料过滤器
+   * @returns {Promise}
+   */
+  public getGroupProfile(options: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getGroupProfile(options);
+        this.currentStore.conversation.groupProfile = imResponse.data.group;
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取群成员资料
+   * get group member profile
+   *
+   * @param {any} options 参数
+   * @param {String} options.groupID 群组ID
+   * @param {Array.<String>} options.userIDList 要查询的群成员用户 ID 列表
+   * @param {	Array.<String>} options.memberCustomFieldFilter 群成员自定义字段筛选
+   * @returns {Promise}
+   */
+  public getGroupMemberProfile(options: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getGroupMemberProfile(
+          options
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 处理申请加群
+   * handling group application
+   * - 管理员
+   *   administrator
+   *
+   * @param {any} options 参数
+   * @param {String} options.handleAction 处理结果 Agree(同意) / Reject(拒绝)
+   * @param {String} options.handleMessage 附言
+   * @param {Message} options.message 对应【群系统通知】的消息实例
+   * @returns {Promise}
+   */
+  public handleGroupApplication(options: any): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.handleGroupApplication(
+          options
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取其他用户资料
+   * get user profile
+   *
+   * @param {Array<string>} userIDList 用户的账号列表/userID list
+   * @returns {Promise}
+   */
+  public async getUserProfile(userIDList: Array<string>) {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getUserProfile({
+          userIDList
+        });
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取 SDK 缓存的好友列表
+   * Get the friend list cached by the SDK
+   *
+   * @param {Array<string>} userIDList 用户的账号列表
+   * @returns {Promise}
+   */
+  public async getFriendList(): Promise<void> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getFriendList();
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 校验好友关系
+   * check friend
+   *
+   * @param {string} userID 用户账号
+   * @returns {Promise}
+   */
+  public async checkFriend(userID: string, type: string): Promise<void> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.checkFriend({
+          userIDList: [userID],
+          type
+        });
+        const isFriendShip = imResponse?.data?.successUserIDList[0]?.relation;
+        resolve(isFriendShip);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取群消息已读成员列表
+   * Get the list of memebers who have read the group message.
+   *
+   * @param {message} message 消息实例/message
+   * @param {string} cursor 分页拉取的游标,第一次拉取传''/Paging pull the cursor,first pull pass ''
+   * @param {number} count 分页拉取的个数/The number of page pulls
+   * @returns {Promise}
+   */
+  public async getGroupReadMemberList(
+    message: any,
+    cursor = '',
+    count = 15
+  ): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getGroupMessageReadMemberList(
+          {
+            message,
+            filter: 0,
+            cursor,
+            count
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 获取群消息未读成员列表
+   * Get the list of memebers who have not read the group message.
+   *
+   * @param {message} message 消息实例/message
+   * @param {string} cursor 分页拉取的游标,第一次拉取传''/Paging pull the cursor,first pull pass ''
+   * @param {number} count 分页拉取的个数/The number of page pulls
+   * @returns {Promise}
+   */
+  public async getGroupUnreadMemberList(
+    message: any,
+    cursor = '',
+    count = 15
+  ): Promise<any> {
+    return this.handlePromiseCallback(async (resolve: any, reject: any) => {
+      try {
+        const imResponse = await this.TUICore.tim.getGroupMessageReadMemberList(
+          {
+            message,
+            filter: 1,
+            cursor,
+            count
+          }
+        );
+        resolve(imResponse);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 自己发送消息上屏显示
+   *
+   * @param {message} message 消息实例/message
+   */
+  public async handleMessageSentByMeToView(message: any) {
+    if (message?.conversationID === this?.store?.conversation?.conversationID) {
+      this.currentStore.messageList.push(message);
+    }
+    return;
+  }
+
+  /**
+   * /////////////////////////////////////////////////////////////////////////////////
+   * //
+   * //                                    UI 数据绑定server数据同步
+   * //                           UI data binding server data synchronization
+   * //
+   * /////////////////////////////////////////////////////////////////////////////////
+   */
+
+  /**
+   * 赋值
+   * bind
+   *
+   * @param {Object} params 使用的数据/params
+   * @returns {Object} 数据/data
+   */
+  public bind(params: any) {
+    return (this.currentStore = params);
+  }
+}

+ 151 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/style/color.scss

@@ -0,0 +1,151 @@
+.TUIChat {
+  &-header {
+    h1 {
+      font-family: PingFangSC-Medium;
+      font-weight: 500;
+      color: #000000;
+      letter-spacing: 0;
+    }
+  }
+
+  .TUIChat-setting {
+    background: #ffffff;
+  }
+
+  &-footer {
+    border-top: 1Px solid #f4f5f9;
+
+    .input {
+      textarea {
+        &::placeholder {
+          color: #dddddd;
+        }
+      }
+
+      p {
+        color: #dddddd;
+      }
+
+      .input-btn {
+        font-weight: 400;
+        background: #006eff;
+        color: #ffffff;
+        letter-spacing: 0;
+
+        &:disabled {
+          opacity: 0.3;
+        }
+
+        &-hover {
+          background: #000000;
+
+          &::before {
+            border: 10Px solid transparent;
+            border-left: 10Px solid #000000;
+          }
+        }
+      }
+    }
+  }
+
+  .disabled {
+    &::before {
+      background: #fbfbfb;
+    }
+  }
+}
+
+.TUI-message-list {
+  .message-more {
+    color: #999999;
+    cursor: pointer;
+  }
+}
+
+.dialog {
+  background: #ffffff;
+
+  &-item {
+    background: #ffffff;
+    border: 1Px solid #e0e0e0;
+    box-shadow: 0 4Px 12Px 0 rgba(0, 0, 0, 0.06);
+
+    p {
+      font-weight: 400;
+      color: #4f4f4f;
+
+      &:hover {
+        background: rgba(0, 110, 255, 0.1);
+      }
+    }
+  }
+
+  &-userInfo {
+    background: rgba(#000000, 0.3);
+
+    .userInfo-main {
+      background: #ffffff;
+
+      main {
+        ol {
+          dl {
+            .userInfo-mask {
+              background: #ffffff;
+              box-shadow: 0 11Px 20Px 0 rgba(0, 0, 0, 0.3);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.image-dialog {
+  background: rgba(0, 0, 0, 0.3);
+
+  header {
+    background: rgba(0, 0, 0, 0.49);
+  }
+}
+
+.btn {
+  font-weight: 400;
+  color: #ffffff;
+  letter-spacing: 0;
+
+  &-cancel {
+    border: 1Px solid #dddddd;
+    color: #666666;
+  }
+
+  &-default {
+    background: #006eff;
+    border: 1Px solid #006eff;
+  }
+
+  &:disabled {
+    opacity: 0.3;
+  }
+}
+
+.toggleMask {
+  &::before {
+    opacity: 0;
+  }
+}
+
+.TUIChat-H5 {
+  .TUIChat-header {
+    background: #ffffff;
+  }
+
+  .TUIChat-footer {
+    background: #ffffff;
+
+    .input {
+      input {
+        background: #f4f5f9;
+      }
+    }
+  }
+}

+ 219 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/style/dist/h5.css

@@ -0,0 +1,219 @@
+.TUIChat-H5 {
+  flex: 1;
+  position: static;
+}
+.TUIChat-H5 .TUIChat-header {
+  width: 100%;
+  box-sizing: border-box;
+  position: sticky;
+  top: 0;
+  z-index: 2;
+}
+.TUIChat-H5 .TUIChat-header .setting {
+  width: 27px;
+}
+.TUIChat-H5 .TUIChat-main .TUI-message-list {
+  height: auto;
+}
+.TUIChat-H5 .TUIChat-footer {
+  width: 100%;
+  box-sizing: border-box;
+  position: sticky;
+  bottom: 0;
+  flex-direction: column-reverse;
+  height: auto;
+  padding: 14px 23px 23px;
+  z-index: 1;
+}
+.TUIChat-H5 .TUIChat-footer .func {
+  display: flex;
+  flex-direction: column;
+}
+.TUIChat-H5 .TUIChat-footer .func-main {
+  display: flex;
+  justify-content: space-between;
+}
+.TUIChat-H5 .TUIChat-footer .reply {
+  order: 1;
+  padding-bottom: 10px;
+}
+.TUIChat-H5 .TUIChat-footer .reply-box {
+  padding: 0;
+}
+.TUIChat-H5 .TUIChat-footer .reply-box i {
+  display: none;
+}
+.TUIChat-H5 .TUIChat-footer .reply-box-show {
+  flex-direction: row;
+}
+.TUIChat-H5 .TUIChat-footer .reply-box-show span {
+  width: auto;
+}
+.TUIChat-H5 .TUIChat-footer .reply-box-show span:first-child {
+  padding-right: 2px;
+}
+.TUIChat-H5 .TUIChat-footer .reply-box-show span:last-child {
+  flex: 1;
+}
+.TUIChat-H5 .TUIChat-footer .input {
+  display: flex;
+  flex-wrap: wrap;
+  flex-direction: row;
+  align-items: flex-end;
+}
+.TUIChat-H5 .TUIChat-footer .input textarea {
+  width: auto;
+  height: auto;
+  padding: 0;
+  flex: 1;
+  background: #f4f5f9;
+  border-radius: 9.4px;
+  padding: 7px 18px;
+  font-size: 16px;
+  line-height: 18px;
+}
+.TUIChat-H5 .TUIChat-footer .input .reference {
+  overflow: hidden;
+  order: 1;
+  width: 100%;
+  max-width: 100%;
+  margin: 0;
+}
+.TUIChat-H5 .TUIChat-footer .input .reference-box {
+  overflow: hidden;
+  padding: 0;
+  width: 100%;
+  max-width: 100%;
+  padding: 10px;
+  margin: 5px 0;
+}
+.TUIChat-H5 .TUIChat-footer .input .reference-box-show {
+  overflow: hidden;
+  width: 0;
+  flex: 1;
+  display: flex;
+  flex-direction: row;
+  text-overflow: ellipsis;
+}
+.TUIChat-H5 .TUIChat-footer .input .reference-box-show span:last-child {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.TUIChat-H5 .TUIChat-footer .input button {
+  position: static !important;
+  margin-left: 7px;
+  word-break: keep-all;
+  height: 32px;
+  flex: 0;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList {
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  background: rgba(0, 0, 0, 0.5);
+  max-height: none;
+  display: flex;
+  align-items: flex-end;
+  z-index: 1;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box {
+  position: static;
+  flex: 1;
+  max-height: 90%;
+  border-radius: 12px 12px 0 0;
+  padding: 14px 23px;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 0;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box-title h1 {
+  font-family: PingFangSC-Medium;
+  font-weight: 500;
+  font-size: 20px;
+  color: #000000;
+  letter-spacing: 0;
+  line-height: 28px;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box-title .close {
+  font-family: PingFangSC-Regular;
+  font-weight: 400;
+  font-size: 18px;
+  color: #3370ff;
+  letter-spacing: 0;
+  line-height: 27px;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box li {
+  padding: 8px 0;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box li img {
+  width: 30px;
+  height: 30px;
+  border-radius: 6.4px;
+  padding: 0;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box li span {
+  font-size: 16px;
+  padding-left: 12px;
+}
+.TUIChat-H5 .TUIChat-footer .input .memberList-box-header span:last-child {
+  padding-left: 0;
+}
+.TUIChat-H5 .manage {
+  position: fixed;
+  height: 100%;
+  top: 0;
+}
+.TUIChat-H5 .mask {
+  position: fixed;
+  width: 100vw;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 9;
+}
+.TUIChat-H5 .mask-main {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 150px;
+  background: white;
+  border-radius: 4px;
+}
+.TUIChat-H5 .mask header {
+  font-family: PingFangSC-Regular;
+  font-weight: 400;
+  font-size: 14px;
+  color: #000000;
+  letter-spacing: 0;
+  text-align: center;
+  padding: 20px 0;
+}
+.TUIChat-H5 .mask footer {
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  border-top: 1px solid #dddddd;
+  height: 40px;
+  align-items: center;
+}
+.TUIChat-H5 .mask footer p {
+  font-family: PingFangSC-Regular;
+  font-weight: 400;
+  font-size: 14px;
+  color: #000000;
+  letter-spacing: 0;
+  text-align: center;
+}
+.TUIChat-H5 .mask footer i {
+  height: 40px;
+  width: 1px;
+  background: #dddddd;
+}

+ 261 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/style/h5.scss

@@ -0,0 +1,261 @@
+.TUIChat-H5 {
+  flex: 1;
+  position: static;
+
+  .TUIChat-header {
+    width: 100%;
+    box-sizing: border-box;
+    position: sticky;
+    top: 0;
+    z-index: 2;
+
+    .setting {
+      width: 27Px;
+    }
+  }
+
+  .TUIChat-main {
+    .TUI-message-list {
+      height: auto;
+    }
+  }
+
+  .TUIChat-footer {
+    width: 100%;
+    box-sizing: border-box;
+    position: sticky;
+    bottom: 0;
+    flex-direction: column-reverse;
+    height: auto;
+    padding: 14Px 23Px 23Px;
+    z-index: 1;
+
+    .func {
+      display: flex;
+      flex-direction: column;
+
+      &-main {
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+
+    .reply {
+      order: 1;
+      padding-bottom: 10Px;
+
+      &-box {
+        padding: 0;
+
+        i {
+          display: none;
+        }
+
+        &-show {
+          flex-direction: row;
+
+          span {
+            width: auto;
+          }
+
+          span:first-child {
+            padding-right: 2Px;
+          }
+
+          span:last-child {
+            flex: 1;
+          }
+        }
+      }
+    }
+
+    .input {
+      display: flex;
+      flex-wrap: wrap;
+      flex-direction: row;
+      align-items: flex-end;
+
+      textarea {
+        width: auto;
+        height: auto;
+        padding: 0;
+        flex: 1;
+        background: #f4f5f9;
+        border-radius: 9.4Px;
+        padding: 7Px 18Px;
+        font-size: 16Px;
+        line-height: 18Px;
+      }
+
+      .reference {
+        overflow: hidden;
+        order: 1;
+        width: 100%;
+        max-width: 100%;
+        margin: 0;
+
+        &-box {
+          overflow: hidden;
+          padding: 0;
+          width: 100%;
+          max-width: 100%;
+          padding: 10Px;
+          margin: 5Px 0;
+
+          &-show {
+            overflow: hidden;
+            width: 0;
+            flex: 1;
+            display: flex;
+            flex-direction: row;
+            text-overflow: ellipsis;
+
+            span:last-child {
+              overflow: hidden;
+              text-overflow: ellipsis;
+            }
+          }
+        }
+      }
+
+      button {
+        position: static !important;
+        margin-left: 7Px;
+        word-break: keep-all;
+        height: 32Px;
+        flex: 0;
+      }
+
+      .memberList {
+        position: fixed;
+        width: 100%;
+        height: 100%;
+        left: 0;
+        top: 0;
+        background: rgba(0, 0, 0, 0.5);
+        max-height: none;
+        display: flex;
+        align-items: flex-end;
+        z-index: 1;
+
+        &-box {
+          position: static;
+          flex: 1;
+          max-height: 90%;
+          border-radius: 12Px 12Px 0 0;
+          padding: 14Px 23Px;
+
+          &-title {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 12Px 0;
+
+            h1 {
+              font-family: PingFangSC-Medium;
+              font-weight: 500;
+              font-size: 20Px;
+              color: #000000;
+              letter-spacing: 0;
+              line-height: 28Px;
+            }
+
+            .close {
+              font-family: PingFangSC-Regular;
+              font-weight: 400;
+              font-size: 18Px;
+              color: #3370ff;
+              letter-spacing: 0;
+              line-height: 27Px;
+            }
+          }
+
+          li {
+            padding: 8Px 0;
+
+            img {
+              width: 30Px;
+              height: 30Px;
+              border-radius: 6.4Px;
+              padding: 0;
+            }
+
+            span {
+              font-size: 16Px;
+              padding-left: 12Px;
+            }
+          }
+
+          &-header {
+            span {
+              &:last-child {
+                padding-left: 0;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .manage {
+    position: fixed;
+    height: 100%;
+    top: 0;
+  }
+
+  .mask {
+    position: fixed;
+    width: 100vw;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(0, 0, 0, 0.5);
+    z-index: 9;
+
+    &-main {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      width: 150Px;
+      background: white;
+      border-radius: 4Px;
+    }
+
+    header {
+      font-family: PingFangSC-Regular;
+      font-weight: 400;
+      font-size: 14Px;
+      color: #000000;
+      letter-spacing: 0;
+      text-align: center;
+      padding: 20Px 0;
+    }
+
+    footer {
+      display: flex;
+      width: 100%;
+      justify-content: space-around;
+      border-top: 1Px solid #dddddd;
+      height: 40Px;
+      align-items: center;
+
+      p {
+        font-family: PingFangSC-Regular;
+        font-weight: 400;
+        font-size: 14Px;
+        color: #000000;
+        letter-spacing: 0;
+        text-align: center;
+      }
+
+      i {
+        height: 40Px;
+        width: 1Px;
+        background: #dddddd;
+      }
+    }
+  }
+}

+ 5 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/style/index.scss

@@ -0,0 +1,5 @@
+@import url('../../../styles/common.scss');
+@import url('../../../styles/icon.scss');
+@import "./color.scss";
+@import "./web.scss";
+@import "./h5.scss";

+ 526 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/style/web.scss

@@ -0,0 +1,526 @@
+.TUIChat {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+
+  &-header {
+    padding: 12Px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 2px solid #DFE1E3;
+    background-color: #F1F2F3;
+
+    h1 {
+      font-size: 16Px;
+      line-height: 30Px;
+    }
+  }
+
+  &-safe-tips {
+    padding: 12Px 20Px;
+    background-color: rgba(255, 149, 0, 0.1);
+    color: #ff8c39;
+    line-height: 18Px;
+    font-family: 'PingFang SC';
+    font-style: normal;
+    font-weight: 400;
+    text-align: justify;
+    font-size: 12Px;
+
+    a {
+      color: #006eff;
+      float: right;
+    }
+  }
+
+  .TUIChat-setting {
+    position: absolute;
+    right: 0;
+    height: calc(100% - 40Px);
+    z-index: 2;
+    top: 40Px;
+  }
+
+  &-main {
+    min-height: 0Px;
+    flex: 1;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+  }
+
+  &-footer {
+    border-top: 2px solid #DFE1E3;
+    height: 173Px;
+    display: flex;
+    flex-direction: column;
+    background-color: #f1f2f3;
+
+    .input {
+      flex: 1;
+      position: relative;
+      display: flex;
+      flex-direction: column;
+
+      textarea {
+        width: 100%;
+        line-height: 20Px;
+        margin: 0;
+        padding: 2Px 20Px;
+        resize: none;
+        box-sizing: border-box;
+        border: none;
+        background: no-repeat;
+
+        &:focus {
+          outline: none;
+          border: none;
+        }
+      }
+
+      p {
+        padding: 12Px 20Px 20Px;
+      }
+
+      .input-btn {
+        position: absolute;
+        bottom: 20Px;
+        right: 20Px;
+        padding: 8Px 20Px;
+        border-radius: 4Px;
+        border: none;
+        font-size: 14Px;
+        text-align: center;
+        line-height: 20Px;
+
+        &:hover {
+          .input-btn-hover {
+            display: flex;
+          }
+        }
+
+        &-hover {
+          display: none;
+          justify-content: center;
+          align-items: center;
+          position: absolute;
+          right: 120%;
+          word-break: keep-all;
+          height: 30Px;
+          white-space: nowrap;
+          top: 0;
+          bottom: 0;
+          margin: auto 0;
+          padding: 5Px 10Px;
+          border-radius: 3Px;
+
+          &::before {
+            content: '';
+            position: absolute;
+            width: 0;
+            height: 0;
+            right: -20Px;
+          }
+        }
+      }
+    }
+  }
+
+  .disabled {
+    position: relative;
+
+    &::before {
+      content: '';
+      position: absolute;
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+
+.TUI-message-list {
+  flex: 1;
+  height: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+  position: relative;
+  background-color: #F1F2F3;
+
+  .message-more {
+    font-size: 0.9rem;
+    padding: 5Px;
+    text-align: center;
+  }
+
+  li:first-child {
+    margin-top: 5Px;
+  }
+
+  li {
+    display: flex;
+    position: relative;
+    padding: 0 20Px 30Px;
+    flex-direction: column;
+
+    .message-label {
+      max-width: 50Px;
+    }
+  }
+
+  .right {
+    flex-direction: row-reverse;
+    justify-content: flex-start;
+  }
+
+  .to-bottom-tip {
+    position: sticky;
+    bottom: 10Px;
+    left: 100%;
+    margin-right: 15Px;
+    width: 92Px;
+    height: 28Px;
+    padding: 0 5Px;
+    background: #ffffff;
+    border: 1Px solid #e0e0e0;
+    box-shadow: 0 4Px 12Px 0 rgba(0, 0, 0, 0.06);
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+    border-radius: 3Px;
+
+    .icon {
+      width: 10Px;
+      height: 10Px;
+    }
+
+    &-cont {
+      font-family: PingFangSC-Regular;
+      font-weight: 400;
+      font-size: 10Px;
+      color: #147aff;
+      letter-spacing: 0;
+      text-align: center;
+      padding-left: 3Px;
+    }
+  }
+}
+
+.dialog {
+  position: absolute;
+  z-index: 5;
+
+  &-item {
+    min-width: min-content;
+    max-width: 220Px;
+    width: 72Px;
+    word-break: keep-all;
+    top: 30Px;
+    border-radius: 8Px;
+    display: flex;
+    flex-wrap: wrap;
+    align-items: baseline;
+    white-space: nowrap;
+
+    li:first-child {
+      margin-top: 0;
+    }
+
+    li {
+      padding: 4Px 12Px;
+      font-size: 12Px;
+      line-height: 17Px;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      flex-direction: row;
+
+      span {
+        padding-left: 4Px;
+      }
+    }
+  }
+
+  &-conversation {
+    .avatar {
+      width: 36Px;
+      height: 36Px;
+      margin: 0 5Px 0 8Px;
+    }
+
+    .name {
+      font-size: 14Px;
+    }
+  }
+
+  &-userInfo {
+    box-sizing: border-box;
+    padding: 0;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: flex-end;
+
+    .userInfo-main {
+      height: 100%;
+      box-sizing: border-box;
+      padding: 20Px;
+      width: 240Px;
+
+      header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+      }
+
+      main {
+        display: flex;
+        padding: 20Px 0;
+
+        ol {
+          flex: 1;
+          display: flex;
+          flex-wrap: wrap;
+
+          dl {
+            position: relative;
+            flex: 0 0 33%;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+
+            img {
+              width: 40Px;
+              height: 40Px;
+            }
+
+            .more {
+              padding-top: 10Px;
+            }
+
+            dd {
+              max-width: 60Px;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+
+            .userInfo-mask {
+              position: absolute;
+              z-index: 5;
+              padding: 20Px;
+              left: 100%;
+
+              li {
+                display: flex;
+                align-items: center;
+
+                label {
+                  width: 60Px;
+                }
+
+                span {
+                  max-width: 200Px;
+                  word-break: keep-all;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.image-dialog {
+  position: fixed;
+  z-index: 5;
+  width: 100vw;
+  height: calc(100vh - 63Px);
+  top: 63Px;
+  left: 0;
+
+  header {
+    display: flex;
+    justify-content: flex-end;
+    width: 100%;
+    box-sizing: border-box;
+    padding: 10Px 10Px;
+  }
+}
+
+.memberList-box {
+  position: absolute;
+  bottom: 128Px;
+  width: 21.94rem;
+  max-height: 10rem;
+  overflow-y: auto;
+  background: #ffffff;
+  box-shadow: 0 0.06rem 0.63rem 0 rgba(2, 16, 43, 0.15);
+  border-radius: 0.13rem;
+
+  &-header {
+    height: 2.5rem;
+    padding-top: 5Px;
+    cursor: pointer;
+
+    &:hover {
+      background: rgba(0, 110, 255, 0.1);
+    }
+  }
+
+  span {
+    font-family: PingFangSC-Regular;
+    font-weight: 400;
+    font-size: 0.88rem;
+    color: #000000;
+    letter-spacing: 0;
+    padding-left: 5Px;
+  }
+
+  &-body {
+    height: 2.5rem;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+
+    &:hover {
+      background: rgba(0, 110, 255, 0.1);
+    }
+  }
+
+  img {
+    width: 1.5rem;
+    height: 1.5rem;
+    padding-left: 10Px;
+  }
+
+  .selected {
+    background: rgba(0, 110, 255, 0.1);
+  }
+}
+
+.reply {
+  display: flex;
+  width: 100%;
+
+  &-box {
+    flex: 1;
+    align-items: center;
+    display: flex;
+    padding: 0 18Px;
+
+    i {
+      height: 3.5rem;
+      border: 1Px solid #e8e8e9;
+    }
+
+    label {
+      margin-top: 5Px;
+    }
+
+    &-show {
+      flex: 1;
+      display: flex;
+      width: 0;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      flex-direction: column;
+      justify-content: center;
+      padding-left: 6Px;
+
+      span {
+        height: 1.25rem;
+        font-family: PingFangSC-Regular;
+        font-weight: 400;
+        font-size: 0.88rem;
+        color: #bfc1c5;
+        letter-spacing: 0;
+        text-overflow: ellipsis;
+        width: 100%;
+        overflow: hidden;
+      }
+    }
+  }
+}
+
+.reference {
+  width: auto;
+  padding-bottom: 0Px;
+  margin: 0Px 100Px 10Px 10Px;
+  display: flex;
+
+  &-box {
+    padding: 0;
+    overflow: hidden;
+    width: max-content;
+    padding: 10Px;
+    background-color: #fbfbfb;
+    display: flex;
+    border-radius: 8Px;
+
+    &-show {
+      width: max-content;
+      padding-right: 10Px;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      overflow: hidden;
+      flex: 1;
+
+      &-name {
+        padding-right: 5Px;
+      }
+
+      span {
+        width: max-content;
+        font-family: 'PingFang SC';
+        font-style: normal;
+        font-weight: 400;
+        font-size: 12Px;
+        line-height: 17Px;
+        color: #666666;
+      }
+    }
+  }
+}
+
+.btn {
+  padding: 8Px 20Px;
+  border-radius: 4Px;
+  border: none;
+  font-size: 14Px;
+  text-align: center;
+  line-height: 20Px;
+}
+
+.toggleMask {
+  &::before {
+    position: fixed;
+    z-index: 1;
+    content: '';
+    width: 100vw;
+    height: 100vh;
+    top: 0;
+    left: 0;
+    opacity: 0;
+  }
+}
+
+::-webkit-scrollbar {
+  width: 6Px;
+  height: 140Px;
+  background-color: transparent;
+}
+
+::-webkit-scrollbar-track {
+  border-radius: 10Px;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 10Px;
+  background-color: #9a999c;
+}

+ 61 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/utils/decodeText.ts

@@ -0,0 +1,61 @@
+import { emojiMap, emojiUrl } from './emojiMap';
+/** 传入messageBody(群系统消息SystemMessage,群提示消息GroupTip除外)
+ * payload = {
+ *  msgType: 'TIMTextElem',
+ *  msgContent: {
+ *    text: 'AAA[龇牙]AAA[龇牙]AAA[龇牙AAA]'
+ *  }
+ *}
+ **/
+export function decodeText(payload:any) {
+  const renderDom = [];
+  // 文本消息
+  let temp = payload.text;
+  let left = -1;
+  let right = -1;
+  while (temp !== '') {
+    left = temp.indexOf('[');
+    right = temp.indexOf(']');
+    switch (left) {
+      case 0:
+        if (right === -1) {
+          renderDom.push({
+            name: 'text',
+            text: temp,
+          });
+          temp = '';
+        } else {
+          const emojiKey = temp.slice(0, right + 1);
+          if (emojiMap[emojiKey]) {
+            renderDom.push({
+              name: 'img',
+              src: emojiUrl + emojiMap[emojiKey],
+            });
+            temp = temp.substring(right + 1);
+          } else {
+            renderDom.push({
+              name: 'text',
+              text: '[',
+            });
+            temp = temp.slice(1);
+          }
+        }
+        break;
+      case -1:
+        renderDom.push({
+          name: 'text',
+          text: temp,
+        });
+        temp = '';
+        break;
+      default:
+        renderDom.push({
+          name: 'text',
+          text: temp.slice(0, left),
+        });
+        temp = temp.substring(left);
+        break;
+    }
+  }
+  return renderDom;
+}

+ 1078 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/utils/dist/utils.js

@@ -0,0 +1,1078 @@
+'use strict';
+var __assign =
+  (this && this.__assign) ||
+  function () {
+    __assign =
+      Object.assign ||
+      function (t) {
+        for (var s, i = 1, n = arguments.length; i < n; i++) {
+          s = arguments[i];
+          for (var p in s)
+            if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
+        }
+        return t;
+      };
+    return __assign.apply(this, arguments);
+  };
+var __spreadArrays =
+  (this && this.__spreadArrays) ||
+  function () {
+    for (var s = 0, i = 0, il = arguments.length; i < il; i++)
+      s += arguments[i].length;
+    for (var r = Array(s), k = 0, i = 0; i < il; i++)
+      for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
+        r[k] = a[j];
+    return r;
+  };
+var _this = this;
+exports.__esModule = true;
+exports.handleSkeletonSize =
+  exports.isMessageTip =
+  exports.throttle =
+  exports.deepCopy =
+  exports.isTypingMessage =
+  exports.JSONToObject =
+  exports.isJSON =
+  exports.handleOptions =
+  exports.isUrl =
+  exports.getImgLoad =
+  exports.translateGroupSystemNotice =
+  exports.handleCustomMessageShowContext =
+  exports.extractCallingInfoFromMessage =
+  exports.handleMergerMessageShowContext =
+  exports.handleFileMessageShowContext =
+  exports.handleAudioMessageShowContext =
+  exports.handleVideoMessageShowContext =
+  exports.handleImageMessageShowContext =
+  exports.handleLocationMessageShowContext =
+  exports.handleFaceMessageShowContext =
+  exports.handleTextMessageShowContext =
+  exports.handleTipMessageShowContext =
+  exports.handleShowLastMessage =
+  exports.handleReferenceForShow =
+  exports.handleAt =
+  exports.handleName =
+  exports.handleAvatar =
+    void 0;
+var date_1 = require('../../../utils/date');
+var decodeText_1 = require('./decodeText');
+var tim_1 = require('../../../../TUICore/tim');
+var constant_1 = require('../../constant');
+// Handling avatars
+function handleAvatar(item) {
+  var _a, _b, _c, _d, _e, _f;
+  var avatar = '';
+  switch (item.type) {
+    case tim_1['default'].TYPES.CONV_C2C:
+      avatar = isUrl(
+        (_a = item === null || item === void 0 ? void 0 : item.userProfile) ===
+          null || _a === void 0
+          ? void 0
+          : _a.avatar
+      )
+        ? (_b =
+            item === null || item === void 0 ? void 0 : item.userProfile) ===
+            null || _b === void 0
+          ? void 0
+          : _b.avatar
+        : 'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690787574969.png';
+      break;
+    case tim_1['default'].TYPES.CONV_GROUP:
+      avatar = isUrl(
+        (_c = item === null || item === void 0 ? void 0 : item.groupProfile) ===
+          null || _c === void 0
+          ? void 0
+          : _c.avatar
+      )
+        ? (_d =
+            item === null || item === void 0 ? void 0 : item.groupProfile) ===
+            null || _d === void 0
+          ? void 0
+          : _d.avatar
+        : 'https://news-info.ks3-cn-beijing.ksyuncs.com/07/1690775328089.png';
+      break;
+    case tim_1['default'].TYPES.CONV_SYSTEM:
+      avatar = isUrl(
+        (_e = item === null || item === void 0 ? void 0 : item.groupProfile) ===
+          null || _e === void 0
+          ? void 0
+          : _e.avatar
+      )
+        ? (_f =
+            item === null || item === void 0 ? void 0 : item.groupProfile) ===
+            null || _f === void 0
+          ? void 0
+          : _f.avatar
+        : 'https://web.sdk.qcloud.com/component/TUIKit/assets/group_avatar.png';
+      break;
+  }
+  return avatar;
+}
+exports.handleAvatar = handleAvatar;
+// Handling names
+function handleName(item) {
+  var _a, _b;
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var name = '';
+  switch (item.type) {
+    case tim_1['default'].TYPES.CONV_C2C:
+      name =
+        (item === null || item === void 0 ? void 0 : item.userProfile.nick) ||
+        ((_a = item === null || item === void 0 ? void 0 : item.userProfile) ===
+          null || _a === void 0
+          ? void 0
+          : _a.userID) ||
+        '';
+      break;
+    case tim_1['default'].TYPES.CONV_GROUP:
+      name =
+        item.groupProfile.name ||
+        ((_b =
+          item === null || item === void 0 ? void 0 : item.groupProfile) ===
+          null || _b === void 0
+          ? void 0
+          : _b.groupID) ||
+        '';
+      break;
+    case tim_1['default'].TYPES.CONV_SYSTEM:
+      name = t('系统通知');
+      break;
+  }
+  return name;
+}
+exports.handleName = handleName;
+// Handle whether there is someone@
+function handleAt(item) {
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var List = [
+    '[' + t('TUIConversation.有人@我') + ']',
+    '[' + t('TUIConversation.@所有人') + ']',
+    '[' +
+      t('TUIConversation.@所有人') +
+      '][' +
+      t('TUIConversation.有人@我') +
+      ']'
+  ];
+  var showAtType = '';
+  for (var index = 0; index < item.groupAtInfoList.length; index++) {
+    if (item.groupAtInfoList[index].atTypeArray[0] && item.unreadCount > 0) {
+      showAtType = List[item.groupAtInfoList[index].atTypeArray[0] - 1];
+    }
+  }
+  return showAtType;
+}
+exports.handleAt = handleAt;
+function handleReferenceForShow(message) {
+  var _a;
+  var data = {
+    referenceMessageForShow: '',
+    referenceMessageType: 0
+  };
+  if (
+    !message ||
+    !(message === null || message === void 0 ? void 0 : message.ID) ||
+    !(message === null || message === void 0 ? void 0 : message.type)
+  )
+    return data;
+  switch (message.type) {
+    case tim_1['default'].TYPES.MSG_TEXT:
+      data.referenceMessageForShow =
+        (_a =
+          message === null || message === void 0 ? void 0 : message.payload) ===
+          null || _a === void 0
+          ? void 0
+          : _a.text;
+      data.referenceMessageType = 1;
+      break;
+    case tim_1['default'].TYPES.MSG_CUSTOM:
+      data.referenceMessageForShow = '[自定义消息]';
+      data.referenceMessageType = 2;
+      break;
+    case tim_1['default'].TYPES.MSG_IMAGE:
+      data.referenceMessageForShow = '[图片]';
+      data.referenceMessageType = 3;
+      break;
+    case tim_1['default'].TYPES.MSG_AUDIO:
+      data.referenceMessageForShow = '[语音]';
+      data.referenceMessageType = 4;
+      break;
+    case tim_1['default'].TYPES.MSG_VIDEO:
+      data.referenceMessageForShow = '[视频]';
+      data.referenceMessageType = 5;
+      break;
+    case tim_1['default'].TYPES.MSG_FILE:
+      data.referenceMessageForShow = '[文件]';
+      data.referenceMessageType = 6;
+      break;
+    case tim_1['default'].TYPES.MSG_FACE:
+      data.referenceMessageForShow = '[表情]';
+      data.referenceMessageType = 8;
+      break;
+  }
+  return data;
+}
+exports.handleReferenceForShow = handleReferenceForShow;
+// Internal display of processing message box
+function handleShowLastMessage(item) {
+  var _a;
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var lastMessage = item.lastMessage;
+  var conversation = item;
+  var showNick = '';
+  var lastMessagePayload = '';
+  // Judge the number of unread messages and display them only when the message is enabled without interruption.
+  var showUnreadCount =
+    conversation.unreadCount > 0 &&
+    conversation.messageRemindType ===
+      tim_1['default'].TYPES.MSG_REMIND_ACPT_NOT_NOTE
+      ? '[' +
+        (conversation.unreadCount > 99 ? '99+' : conversation.unreadCount) +
+        t('TUIConversation.条') +
+        '] '
+      : '';
+  // Determine the lastmessage sender of the group. Namecard / Nick / userid is displayed by priority
+  if (conversation.type === tim_1['default'].TYPES.CONV_GROUP) {
+    if (lastMessage.fromAccount === conversation.groupProfile.selfInfo.userID) {
+      showNick = t('TUIConversation.我');
+    } else {
+      showNick =
+        lastMessage.nameCard || lastMessage.nick || lastMessage.fromAccount;
+    }
+  }
+  // Display content of lastmessage message body
+  if (lastMessage.type === tim_1['default'].TYPES.MSG_TEXT) {
+    lastMessagePayload = lastMessage.payload.text;
+  } else if (lastMessage.type === tim_1['default'].TYPES.MSG_CUSTOM) {
+    var data = JSONToObject(
+      (_a =
+        lastMessage === null || lastMessage === void 0
+          ? void 0
+          : lastMessage.payload) === null || _a === void 0
+        ? void 0
+        : _a.data
+    );
+    if ((data === null || data === void 0 ? void 0 : data.businessID) === 1) {
+      lastMessagePayload = extractCallingInfoFromMessage(lastMessage);
+      return lastMessagePayload;
+    }
+    lastMessagePayload = lastMessage.messageForShow;
+  } else {
+    lastMessagePayload = lastMessage.messageForShow;
+  }
+  if (lastMessage.isRevoked) {
+    lastMessagePayload = t('TUIChat.撤回了一条消息');
+  }
+  if (
+    conversation.type === tim_1['default'].TYPES.CONV_GROUP &&
+    lastMessage.type === tim_1['default'].TYPES.MSG_GRP_TIP
+  ) {
+    return lastMessagePayload;
+  }
+  // Specific display content of message box
+  return (
+    '' + showUnreadCount + (showNick ? showNick + ':' : '') + lastMessagePayload
+  );
+}
+exports.handleShowLastMessage = handleShowLastMessage;
+// Handling system tip message display
+function handleTipMessageShowContext(message) {
+  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var options = {
+    message: message,
+    text: ''
+  };
+  var userName =
+    (message === null || message === void 0 ? void 0 : message.nick) ||
+    ((_b =
+      (_a =
+        message === null || message === void 0 ? void 0 : message.payload) ===
+        null || _a === void 0
+        ? void 0
+        : _a.userIDList) === null || _b === void 0
+      ? void 0
+      : _b.join(','));
+  if (
+    ((_d =
+      (_c =
+        message === null || message === void 0 ? void 0 : message.payload) ===
+        null || _c === void 0
+        ? void 0
+        : _c.memberList) === null || _d === void 0
+      ? void 0
+      : _d.length) > 0
+  ) {
+    userName = '';
+    (_f =
+      (_e =
+        message === null || message === void 0 ? void 0 : message.payload) ===
+        null || _e === void 0
+        ? void 0
+        : _e.memberList) === null || _f === void 0
+      ? void 0
+      : _f.map(function (user) {
+          userName +=
+            ((user === null || user === void 0 ? void 0 : user.nick) ||
+              (user === null || user === void 0 ? void 0 : user.userID)) + ',';
+        });
+    userName =
+      userName === null || userName === void 0 ? void 0 : userName.slice(0, -1);
+  }
+  if (
+    (message === null || message === void 0 ? void 0 : message.type) ===
+    ((_g =
+      tim_1['default'] === null || tim_1['default'] === void 0
+        ? void 0
+        : tim_1['default'].TYPES) === null || _g === void 0
+      ? void 0
+      : _g.MSG_GRP_TIP)
+  ) {
+    switch (message.payload.operationType) {
+      case tim_1['default'].TYPES.GRP_TIP_MBR_JOIN:
+        options.text = userName + ' ' + t('message.tip.加入群组');
+        break;
+      case tim_1['default'].TYPES.GRP_TIP_MBR_QUIT:
+        options.text =
+          t('message.tip.群成员') +
+          '\uFF1A' +
+          userName +
+          ' ' +
+          t('message.tip.退出群组');
+        break;
+      case tim_1['default'].TYPES.GRP_TIP_MBR_KICKED_OUT:
+        options.text =
+          t('message.tip.群成员') +
+          '\uFF1A' +
+          userName +
+          ' ' +
+          t('message.tip.被') +
+          message.payload.operatorID +
+          t('message.tip.踢出群组');
+        break;
+      case tim_1['default'].TYPES.GRP_TIP_MBR_SET_ADMIN:
+        options.text =
+          t('message.tip.群成员') +
+          '\uFF1A' +
+          userName +
+          ' ' +
+          t('message.tip.成为管理员');
+        break;
+      case tim_1['default'].TYPES.GRP_TIP_MBR_CANCELED_ADMIN:
+        options.text =
+          t('message.tip.群成员') +
+          '\uFF1A' +
+          userName +
+          ' ' +
+          t('message.tip.被撤销管理员');
+        break;
+      case tim_1['default'].TYPES.GRP_TIP_GRP_PROFILE_UPDATED:
+        // options.text =  `${userName} 修改群组资料`;
+        options.text = handleTipGrpUpdated(message);
+        break;
+      case tim_1['default'].TYPES.GRP_TIP_MBR_PROFILE_UPDATED:
+        for (
+          var _i = 0, _k = message.payload.memberList;
+          _i < _k.length;
+          _i++
+        ) {
+          var member = _k[_i];
+          if (member.muteTime > 0) {
+            options.text =
+              t('message.tip.群成员') +
+              '\uFF1A' +
+              member.userID +
+              t('message.tip.被禁言');
+          } else {
+            options.text =
+              t('message.tip.群成员') +
+              '\uFF1A' +
+              member.userID +
+              t('message.tip.被取消禁言');
+          }
+        }
+        break;
+      default:
+        options.text = '[' + t('message.tip.群提示消息') + ']';
+        break;
+    }
+  } else if (
+    ((_h =
+      message === null || message === void 0 ? void 0 : message.payload) ===
+      null || _h === void 0
+      ? void 0
+      : _h.data) === 'group_create'
+  ) {
+    options.text =
+      (_j =
+        message === null || message === void 0 ? void 0 : message.payload) ===
+        null || _j === void 0
+        ? void 0
+        : _j.extension;
+  } else {
+    options.text = extractCallingInfoFromMessage(message);
+  }
+  return options;
+}
+exports.handleTipMessageShowContext = handleTipMessageShowContext;
+function handleTipGrpUpdated(message) {
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var payload = message.payload;
+  var newGroupProfile = payload.newGroupProfile;
+  var operatorID = payload.operatorID;
+  var text = '';
+  if ('muteAllMembers' in newGroupProfile) {
+    if (newGroupProfile['muteAllMembers']) {
+      text =
+        t('message.tip.管理员') +
+        ' ' +
+        operatorID +
+        ' ' +
+        t('message.tip.开启全员禁言');
+    } else {
+      text =
+        t('message.tip.管理员') +
+        ' ' +
+        operatorID +
+        ' ' +
+        t('message.tip.取消全员禁言');
+    }
+  } else if ('ownerID' in newGroupProfile) {
+    text = newGroupProfile['ownerID'] + ' ' + t('message.tip.成为新的群主');
+  } else if ('groupName' in newGroupProfile) {
+    text =
+      operatorID +
+      ' ' +
+      t('message.tip.修改群名为') +
+      ' ' +
+      newGroupProfile['groupName'];
+  } else if ('notification' in newGroupProfile) {
+    text = operatorID + ' ' + t('message.tip.发布新公告');
+  }
+  return text;
+}
+// Parsing and handling text message display
+function handleTextMessageShowContext(item) {
+  var options = {
+    text: decodeText_1.decodeText(item.payload)
+  };
+  return options;
+}
+exports.handleTextMessageShowContext = handleTextMessageShowContext;
+// Parsing and handling face message display
+function handleFaceMessageShowContext(item) {
+  var face = {
+    message: item,
+    name: '',
+    url: ''
+  };
+  face.name = item.payload.data;
+  if (item.payload.data.indexOf('@2x') < 0) {
+    face.name = face.name + '@2x';
+  }
+  face.url =
+    'https://web.sdk.qcloud.com/im/assets/face-elem/' + face.name + '.png';
+  return face;
+}
+exports.handleFaceMessageShowContext = handleFaceMessageShowContext;
+// Parsing and handling location message display
+function handleLocationMessageShowContext(item) {
+  var location = {
+    lon: '',
+    lat: '',
+    href: '',
+    url: '',
+    description: '',
+    message: item
+  };
+  location.lon = item.payload.longitude.toFixed(6);
+  location.lat = item.payload.latitude.toFixed(6);
+  location.href =
+    'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&' +
+    ('pointx=' +
+      location.lon +
+      '&pointy=' +
+      location.lat +
+      '&name=' +
+      item.payload.description);
+  location.url =
+    'https://apis.map.qq.com/ws/staticmap/v2/?' +
+    ('center=' +
+      location.lat +
+      ',' +
+      location.lon +
+      '&zoom=10&size=300*150&maptype=roadmap&') +
+    ('markers=size:large|color:0xFFCCFF|label:k|' +
+      location.lat +
+      ',' +
+      location.lon +
+      '&') +
+    'key=UBNBZ-PTP3P-TE7DB-LHRTI-Y4YLE-VWBBD';
+  location.description = item.payload.description;
+  return location;
+}
+exports.handleLocationMessageShowContext = handleLocationMessageShowContext;
+// Parsing and handling image message display
+function handleImageMessageShowContext(item) {
+  return {
+    progress:
+      (item === null || item === void 0 ? void 0 : item.status) === 'unSend' &&
+      item.progress,
+    url: item.payload.imageInfoArray[1].url,
+    width: item.payload.imageInfoArray[0].width,
+    height: item.payload.imageInfoArray[0].height,
+    message: item
+  };
+}
+exports.handleImageMessageShowContext = handleImageMessageShowContext;
+// Parsing and handling video message display
+function handleVideoMessageShowContext(item) {
+  var _a, _b, _c, _d;
+  return {
+    progress:
+      (item === null || item === void 0 ? void 0 : item.status) === 'unSend' &&
+      (item === null || item === void 0 ? void 0 : item.progress),
+    url:
+      (_a = item === null || item === void 0 ? void 0 : item.payload) ===
+        null || _a === void 0
+        ? void 0
+        : _a.videoUrl,
+    snapshotUrl:
+      (_b = item === null || item === void 0 ? void 0 : item.payload) ===
+        null || _b === void 0
+        ? void 0
+        : _b.snapshotUrl,
+    snapshotWidth:
+      (_c = item === null || item === void 0 ? void 0 : item.payload) ===
+        null || _c === void 0
+        ? void 0
+        : _c.snapshotWidth,
+    snapshotHeight:
+      (_d = item === null || item === void 0 ? void 0 : item.payload) ===
+        null || _d === void 0
+        ? void 0
+        : _d.snapshotHeight,
+    message: item
+  };
+}
+exports.handleVideoMessageShowContext = handleVideoMessageShowContext;
+// Parsing and handling audio message display
+function handleAudioMessageShowContext(item) {
+  return {
+    progress:
+      (item === null || item === void 0 ? void 0 : item.status) === 'unSend' &&
+      item.progress,
+    url: item.payload.url,
+    message: item,
+    second: item.payload.second
+  };
+}
+exports.handleAudioMessageShowContext = handleAudioMessageShowContext;
+// Parsing and handling file message display
+function handleFileMessageShowContext(item) {
+  var size = '';
+  if (item.payload.fileSize >= 1024 * 1024) {
+    size = (item.payload.fileSize / (1024 * 1024)).toFixed(2) + ' Mb';
+  } else if (item.payload.fileSize >= 1024) {
+    size = (item.payload.fileSize / 1024).toFixed(2) + ' Kb';
+  } else {
+    size = item.payload.fileSize.toFixed(2) + 'B';
+  }
+  return {
+    progress:
+      (item === null || item === void 0 ? void 0 : item.status) === 'unSend' &&
+      item.progress,
+    url: item.payload.fileUrl,
+    message: item,
+    name: item.payload.fileName,
+    size: size
+  };
+}
+exports.handleFileMessageShowContext = handleFileMessageShowContext;
+// Parsing and handling merger message display
+function handleMergerMessageShowContext(item) {
+  return __assign({ message: item }, item.payload);
+}
+exports.handleMergerMessageShowContext = handleMergerMessageShowContext;
+// Parse audio and video call messages
+function extractCallingInfoFromMessage(message) {
+  var _a, _b;
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var callingMessage = {};
+  var objectData = {};
+  try {
+    callingMessage = JSONToObject(
+      (_a =
+        message === null || message === void 0 ? void 0 : message.payload) ===
+        null || _a === void 0
+        ? void 0
+        : _a.data
+    );
+  } catch (error) {
+    callingMessage = {};
+  }
+  if (callingMessage.businessID !== 1) {
+    return '';
+  }
+  try {
+    objectData = JSONToObject(callingMessage.data);
+  } catch (error) {
+    objectData = {};
+  }
+  var inviteeList = '';
+  (_b =
+    callingMessage === null || callingMessage === void 0
+      ? void 0
+      : callingMessage.inviteeList) === null || _b === void 0
+    ? void 0
+    : _b.forEach(function (userID, index) {
+        var _a;
+        if (
+          index <
+          ((_a =
+            callingMessage === null || callingMessage === void 0
+              ? void 0
+              : callingMessage.inviteeList) === null || _a === void 0
+            ? void 0
+            : _a.length) -
+            1
+        ) {
+          inviteeList += '"' + userID + '"\u3001';
+        } else {
+          inviteeList += '"' + userID + '" ';
+        }
+      });
+  var inviter =
+    '"' +
+    (callingMessage === null || callingMessage === void 0
+      ? void 0
+      : callingMessage.inviter) +
+    '" ';
+  switch (callingMessage.actionType) {
+    case 1: {
+      if (objectData.call_end >= 0 && !callingMessage.groupID) {
+        return (
+          t('message.custom.通话时长') +
+          '\uFF1A' +
+          date_1.formatTime(objectData.call_end)
+        );
+      }
+      if (callingMessage.groupID && callingMessage.timeout > 0) {
+        return '' + inviter + t('message.custom.发起通话');
+      }
+      if (callingMessage.groupID) {
+        return '' + t('message.custom.结束群聊');
+      }
+      if (objectData.data && objectData.data.cmd === 'switchToAudio') {
+        return '' + t('message.custom.切换语音通话');
+      }
+      if (objectData.data && objectData.data.cmd === 'switchToVideo') {
+        return '' + t('message.custom.切换视频通话');
+      }
+      return '' + t('message.custom.发起通话');
+    }
+    case 2:
+      return (
+        '' +
+        (callingMessage.groupID ? inviter : '') +
+        t('message.custom.取消通话')
+      );
+    case 3:
+      if (objectData.data && objectData.data.cmd === 'switchToAudio') {
+        return '' + t('message.custom.切换语音通话');
+      }
+      if (objectData.data && objectData.data.cmd === 'switchToVideo') {
+        return '' + t('message.custom.切换视频通话');
+      }
+      return (
+        '' +
+        (callingMessage.groupID ? inviteeList : '') +
+        t('message.custom.已接听')
+      );
+    case 4:
+      return (
+        '' +
+        (callingMessage.groupID ? inviteeList : '') +
+        t('message.custom.拒绝通话')
+      );
+    case 5:
+      if (objectData.data && objectData.data.cmd === 'switchToAudio') {
+        return '' + t('message.custom.切换语音通话');
+      }
+      if (objectData.data && objectData.data.cmd === 'switchToVideo') {
+        return '' + t('message.custom.切换视频通话');
+      }
+      return (
+        '' +
+        (callingMessage.groupID ? inviteeList : '') +
+        t('message.custom.无应答')
+      );
+    default:
+      return '';
+  }
+}
+exports.extractCallingInfoFromMessage = extractCallingInfoFromMessage;
+// Parsing and handling custom message display
+function handleCustomMessageShowContext(item) {
+  var _a;
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var payloadObj = JSONToObject(
+    (_a = item === null || item === void 0 ? void 0 : item.payload) === null ||
+      _a === void 0
+      ? void 0
+      : _a.data
+  );
+  if (
+    (payloadObj === null || payloadObj === void 0
+      ? void 0
+      : payloadObj.businessID) === constant_1['default'].typeEvaluate
+  ) {
+    if (
+      !(
+        (payloadObj === null || payloadObj === void 0
+          ? void 0
+          : payloadObj.score) > 0
+      )
+    ) {
+      payloadObj.score = 1;
+      item.payload.data = JSON.stringify(payloadObj);
+    }
+  }
+  return {
+    message: item,
+    custom:
+      extractCallingInfoFromMessage(item) ||
+      '[' + t('message.custom.自定义消息') + ']'
+  };
+}
+exports.handleCustomMessageShowContext = handleCustomMessageShowContext;
+// Parsing and handling system message display
+function translateGroupSystemNotice(message) {
+  var t = window.TUIKitTUICore.config.i18n.useI18n().t;
+  var groupName =
+    message.payload.groupProfile.name || message.payload.groupProfile.groupID;
+  switch (message.payload.operationType) {
+    case 1:
+      return (
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.申请加入群组') +
+        '\uFF1A' +
+        groupName
+      );
+    case 2:
+      return t('message.tip.成功加入群组') + '\uFF1A' + groupName;
+    case 3:
+      return (
+        t('message.tip.申请加入群组') +
+        '\uFF1A' +
+        groupName +
+        ' ' +
+        t('message.tip.被拒绝')
+      );
+    case 4:
+      return (
+        '' +
+        t('message.tip.你被管理员') +
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.踢出群组') +
+        '\uFF1A' +
+        groupName
+      );
+    case 5:
+      return (
+        t('message.tip.群') +
+        '\uFF1A' +
+        groupName +
+        ' ' +
+        t('message.tip.被') +
+        ' ' +
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.解散')
+      );
+    case 6:
+      return (
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.创建群') +
+        '\uFF1A' +
+        groupName
+      );
+    case 7:
+      return (
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.邀请你加群') +
+        '\uFF1A' +
+        groupName
+      );
+    case 8:
+      return t('message.tip.你退出群组') + '\uFF1A' + groupName;
+    case 9:
+      return (
+        '' +
+        t('message.tip.你被') +
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.设置为群') +
+        '\uFF1A' +
+        groupName +
+        ' ' +
+        t('message.tip.的管理员')
+      );
+    case 10:
+      return (
+        '' +
+        t('message.tip.你被') +
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.撤销群') +
+        '\uFF1A' +
+        groupName +
+        ' ' +
+        t('message.tip.的管理员身份')
+      );
+    case 12:
+      return (
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.邀请你加群') +
+        '\uFF1A' +
+        groupName
+      );
+    case 13:
+      return (
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.同意加群') +
+        '\uFF1A' +
+        groupName
+      );
+    case 14:
+      return (
+        message.payload.operatorID +
+        ' ' +
+        t('message.tip.拒接加群') +
+        '\uFF1A' +
+        groupName
+      );
+    case 255:
+      return (
+        t('message.tip.自定义群系统通知') +
+        ': ' +
+        message.payload.userDefinedField
+      );
+  }
+}
+exports.translateGroupSystemNotice = translateGroupSystemNotice;
+// Image loading complete
+function getImgLoad(container, className, callback) {
+  var images =
+    (container === null || container === void 0
+      ? void 0
+      : container.querySelectorAll('.' + className)) || [];
+  var promiseList = Array.prototype.slice.call(images).map(function (node) {
+    return new Promise(function (resolve, reject) {
+      node.onload = function () {
+        resolve(node);
+      };
+      node.onloadeddata = function () {
+        resolve(node);
+      };
+      node.onprogress = function () {
+        resolve(node);
+      };
+      if (node.complete) {
+        resolve(node);
+      }
+    });
+  });
+  return Promise.all(promiseList)
+    .then(function () {
+      callback && callback();
+    })
+    ['catch'](function (e) {
+      console.error('网络异常', e);
+    });
+}
+exports.getImgLoad = getImgLoad;
+// Determine whether it is url
+function isUrl(url) {
+  return /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(
+    url
+  );
+}
+exports.isUrl = isUrl;
+// Handling custom message options
+function handleOptions(businessID, version, other) {
+  return __assign({ businessID: businessID, version: version }, other);
+}
+exports.handleOptions = handleOptions;
+// Determine if it is a JSON string
+function isJSON(str) {
+  // eslint-disable-next-line no-useless-escape
+  if (typeof str === 'string') {
+    try {
+      var data = JSON.parse(str);
+      if (data) {
+        return true;
+      }
+      return false;
+    } catch (error) {
+      return false;
+    }
+  }
+  return false;
+}
+exports.isJSON = isJSON;
+// Determine if it is a JSON string
+function JSONToObject(str) {
+  if (!str || !isJSON(str)) {
+    return str;
+  }
+  return JSON.parse(str);
+}
+exports.JSONToObject = JSONToObject;
+// Determine if it is a typing message
+function isTypingMessage(item) {
+  var _a;
+  if (!item) return false;
+  try {
+    var businessID = JSONToObject(
+      (_a = item === null || item === void 0 ? void 0 : item.payload) ===
+        null || _a === void 0
+        ? void 0
+        : _a.data
+    ).businessID;
+    if (businessID === constant_1['default'].typeUserTyping) return true;
+  } catch (_b) {
+    return false;
+  }
+  return false;
+}
+exports.isTypingMessage = isTypingMessage;
+function deepCopy(data, hash) {
+  if (hash === void 0) {
+    hash = new WeakMap();
+  }
+  if (typeof data !== 'object' || data === null) {
+    throw new TypeError('传入参数不是对象');
+  }
+  if (hash.has(data)) {
+    return hash.get(data);
+  }
+  var newData = Object.create(Object.getPrototypeOf(data));
+  var dataKeys = Object.keys(data);
+  dataKeys.forEach(function (value) {
+    var currentDataValue = data[value];
+    if (typeof currentDataValue !== 'object' || currentDataValue === null) {
+      newData[value] = currentDataValue;
+    } else if (Array.isArray(currentDataValue)) {
+      newData[value] = __spreadArrays(currentDataValue);
+    } else if (currentDataValue instanceof Set) {
+      newData[value] = new Set(__spreadArrays(currentDataValue));
+    } else if (currentDataValue instanceof Map) {
+      newData[value] = new Map(__spreadArrays(currentDataValue));
+    } else {
+      hash.set(data, data);
+      newData[value] = deepCopy(currentDataValue, hash);
+    }
+  });
+  return newData;
+}
+exports.deepCopy = deepCopy;
+exports.throttle = function (fn) {
+  var isRunning = false;
+  return function () {
+    var args = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+      args[_i] = arguments[_i];
+    }
+    if (isRunning) return;
+    setTimeout(function () {
+      fn.apply(_this, args);
+      isRunning = false;
+    }, 100);
+  };
+};
+exports.isMessageTip = function (message) {
+  var _a, _b, _c, _d, _e, _f, _g, _h;
+  if (
+    (message === null || message === void 0 ? void 0 : message.type) ===
+      ((_a =
+        tim_1['default'] === null || tim_1['default'] === void 0
+          ? void 0
+          : tim_1['default'].TYPES) === null || _a === void 0
+        ? void 0
+        : _a.MSG_GRP_TIP) ||
+    ((message === null || message === void 0 ? void 0 : message.type) ===
+      ((_b =
+        tim_1['default'] === null || tim_1['default'] === void 0
+          ? void 0
+          : tim_1['default'].TYPES) === null || _b === void 0
+        ? void 0
+        : _b.MSG_CUSTOM) &&
+      (message === null || message === void 0
+        ? void 0
+        : message.conversationType) ===
+        ((_c =
+          tim_1['default'] === null || tim_1['default'] === void 0
+            ? void 0
+            : tim_1['default'].TYPES) === null || _c === void 0
+          ? void 0
+          : _c.CONV_GROUP) &&
+      ((_e = JSONToObject(
+        (_d =
+          message === null || message === void 0 ? void 0 : message.payload) ===
+          null || _d === void 0
+          ? void 0
+          : _d.data
+      )) === null || _e === void 0
+        ? void 0
+        : _e.businessID) ===
+        (constant_1['default'] === null || constant_1['default'] === void 0
+          ? void 0
+          : constant_1['default'].TYPE_CALL_MESSAGE)) ||
+    ((message === null || message === void 0 ? void 0 : message.type) ===
+      ((_f =
+        tim_1['default'] === null || tim_1['default'] === void 0
+          ? void 0
+          : tim_1['default'].TYPES) === null || _f === void 0
+        ? void 0
+        : _f.MSG_CUSTOM) &&
+      (message === null || message === void 0
+        ? void 0
+        : message.conversationType) ===
+        ((_g =
+          tim_1['default'] === null || tim_1['default'] === void 0
+            ? void 0
+            : tim_1['default'].TYPES) === null || _g === void 0
+          ? void 0
+          : _g.CONV_GROUP) &&
+      ((_h =
+        message === null || message === void 0 ? void 0 : message.payload) ===
+        null || _h === void 0
+        ? void 0
+        : _h.data) === 'group_create')
+  ) {
+    return true;
+  }
+  return false;
+};
+exports.handleSkeletonSize = function (width, height, maxWidth, maxHeight) {
+  var widthToHeight = width / height;
+  var maxWidthToHeight = maxWidth / maxHeight;
+  if (width <= maxWidth && height <= maxHeight) {
+    return { width: width, height: height };
+  } else if (
+    (width <= maxWidth && height > maxHeight) ||
+    (width > maxWidth &&
+      height > maxHeight &&
+      widthToHeight <= maxWidthToHeight)
+  ) {
+    return { width: width * (maxHeight / height), height: maxHeight };
+  } else {
+    return { width: maxWidth, height: height * (maxWidth / width) };
+  }
+};

+ 304 - 0
src/TUIKit/TUIComponents/container/TUIChat-temp/utils/emojiMap.ts

@@ -0,0 +1,304 @@
+export const emojiUrl = 'https://web.sdk.qcloud.com/im/assets/emoji/';
+export const emojiMap:any = {
+  '[NO]': 'emoji_0@2x.png',
+  '[OK]': 'emoji_1@2x.png',
+  '[下雨]': 'emoji_2@2x.png',
+  '[么么哒]': 'emoji_3@2x.png',
+  '[乒乓]': 'emoji_4@2x.png',
+  '[便便]': 'emoji_5@2x.png',
+  '[信封]': 'emoji_6@2x.png',
+  '[偷笑]': 'emoji_7@2x.png',
+  '[傲慢]': 'emoji_8@2x.png',
+  '[再见]': 'emoji_9@2x.png',
+  '[冷汗]': 'emoji_10@2x.png',
+  '[凋谢]': 'emoji_11@2x.png',
+  '[刀]': 'emoji_12@2x.png',
+  '[删除]': 'emoji_13@2x.png',
+  '[勾引]': 'emoji_14@2x.png',
+  '[发呆]': 'emoji_15@2x.png',
+  '[发抖]': 'emoji_16@2x.png',
+  '[可怜]': 'emoji_17@2x.png',
+  '[可爱]': 'emoji_18@2x.png',
+  '[右哼哼]': 'emoji_19@2x.png',
+  '[右太极]': 'emoji_20@2x.png',
+  '[右车头]': 'emoji_21@2x.png',
+  '[吐]': 'emoji_22@2x.png',
+  '[吓]': 'emoji_23@2x.png',
+  '[咒骂]': 'emoji_24@2x.png',
+  '[咖啡]': 'emoji_25@2x.png',
+  '[啤酒]': 'emoji_26@2x.png',
+  '[嘘]': 'emoji_27@2x.png',
+  '[回头]': 'emoji_28@2x.png',
+  '[困]': 'emoji_29@2x.png',
+  '[坏笑]': 'emoji_30@2x.png',
+  '[多云]': 'emoji_31@2x.png',
+  '[大兵]': 'emoji_32@2x.png',
+  '[大哭]': 'emoji_33@2x.png',
+  '[太阳]': 'emoji_34@2x.png',
+  '[奋斗]': 'emoji_35@2x.png',
+  '[奶瓶]': 'emoji_36@2x.png',
+  '[委屈]': 'emoji_37@2x.png',
+  '[害羞]': 'emoji_38@2x.png',
+  '[尴尬]': 'emoji_39@2x.png',
+  '[左哼哼]': 'emoji_40@2x.png',
+  '[左太极]': 'emoji_41@2x.png',
+  '[左车头]': 'emoji_42@2x.png',
+  '[差劲]': 'emoji_43@2x.png',
+  '[弱]': 'emoji_44@2x.png',
+  '[强]': 'emoji_45@2x.png',
+  '[彩带]': 'emoji_46@2x.png',
+  '[彩球]': 'emoji_47@2x.png',
+  '[得意]': 'emoji_48@2x.png',
+  '[微笑]': 'emoji_49@2x.png',
+  '[心碎了]': 'emoji_50@2x.png',
+  '[快哭了]': 'emoji_51@2x.png',
+  '[怄火]': 'emoji_52@2x.png',
+  '[怒]': 'emoji_53@2x.png',
+  '[惊恐]': 'emoji_54@2x.png',
+  '[惊讶]': 'emoji_55@2x.png',
+  '[憨笑]': 'emoji_56@2x.png',
+  '[手枪]': 'emoji_57@2x.png',
+  '[打哈欠]': 'emoji_58@2x.png',
+  '[抓狂]': 'emoji_59@2x.png',
+  '[折磨]': 'emoji_60@2x.png',
+  '[抠鼻]': 'emoji_61@2x.png',
+  '[抱抱]': 'emoji_62@2x.png',
+  '[抱拳]': 'emoji_63@2x.png',
+  '[拳头]': 'emoji_64@2x.png',
+  '[挥手]': 'emoji_65@2x.png',
+  '[握手]': 'emoji_66@2x.png',
+  '[撇嘴]': 'emoji_67@2x.png',
+  '[擦汗]': 'emoji_68@2x.png',
+  '[敲打]': 'emoji_69@2x.png',
+  '[晕]': 'emoji_70@2x.png',
+  '[月亮]': 'emoji_71@2x.png',
+  '[棒棒糖]': 'emoji_72@2x.png',
+  '[汽车]': 'emoji_73@2x.png',
+  '[沙发]': 'emoji_74@2x.png',
+  '[流汗]': 'emoji_75@2x.png',
+  '[流泪]': 'emoji_76@2x.png',
+  '[激动]': 'emoji_77@2x.png',
+  '[灯泡]': 'emoji_78@2x.png',
+  '[炸弹]': 'emoji_79@2x.png',
+  '[熊猫]': 'emoji_80@2x.png',
+  '[爆筋]': 'emoji_81@2x.png',
+  '[爱你]': 'emoji_82@2x.png',
+  '[爱心]': 'emoji_83@2x.png',
+  '[爱情]': 'emoji_84@2x.png',
+  '[猪头]': 'emoji_85@2x.png',
+  '[猫咪]': 'emoji_86@2x.png',
+  '[献吻]': 'emoji_87@2x.png',
+  '[玫瑰]': 'emoji_88@2x.png',
+  '[瓢虫]': 'emoji_89@2x.png',
+  '[疑问]': 'emoji_90@2x.png',
+  '[白眼]': 'emoji_91@2x.png',
+  '[皮球]': 'emoji_92@2x.png',
+  '[睡觉]': 'emoji_93@2x.png',
+  '[磕头]': 'emoji_94@2x.png',
+  '[示爱]': 'emoji_95@2x.png',
+  '[礼品袋]': 'emoji_96@2x.png',
+  '[礼物]': 'emoji_97@2x.png',
+  '[篮球]': 'emoji_98@2x.png',
+  '[米饭]': 'emoji_99@2x.png',
+  '[糗大了]': 'emoji_100@2x.png',
+  '[红双喜]': 'emoji_101@2x.png',
+  '[红灯笼]': 'emoji_102@2x.png',
+  '[纸巾]': 'emoji_103@2x.png',
+  '[胜利]': 'emoji_104@2x.png',
+  '[色]': 'emoji_105@2x.png',
+  '[药]': 'emoji_106@2x.png',
+  '[菜刀]': 'emoji_107@2x.png',
+  '[蛋糕]': 'emoji_108@2x.png',
+  '[蜡烛]': 'emoji_109@2x.png',
+  '[街舞]': 'emoji_110@2x.png',
+  '[衰]': 'emoji_111@2x.png',
+  '[西瓜]': 'emoji_112@2x.png',
+  '[调皮]': 'emoji_113@2x.png',
+  '[象棋]': 'emoji_114@2x.png',
+  '[跳绳]': 'emoji_115@2x.png',
+  '[跳跳]': 'emoji_116@2x.png',
+  '[车厢]': 'emoji_117@2x.png',
+  '[转圈]': 'emoji_118@2x.png',
+  '[鄙视]': 'emoji_119@2x.png',
+  '[酷]': 'emoji_120@2x.png',
+  '[钞票]': 'emoji_121@2x.png',
+  '[钻戒]': 'emoji_122@2x.png',
+  '[闪电]': 'emoji_123@2x.png',
+  '[闭嘴]': 'emoji_124@2x.png',
+  '[闹钟]': 'emoji_125@2x.png',
+  '[阴险]': 'emoji_126@2x.png',
+  '[难过]': 'emoji_127@2x.png',
+  '[雨伞]': 'emoji_128@2x.png',
+  '[青蛙]': 'emoji_129@2x.png',
+  '[面条]': 'emoji_130@2x.png',
+  '[鞭炮]': 'emoji_131@2x.png',
+  '[风车]': 'emoji_132@2x.png',
+  '[飞吻]': 'emoji_133@2x.png',
+  '[飞机]': 'emoji_134@2x.png',
+  '[饥饿]': 'emoji_135@2x.png',
+  '[香蕉]': 'emoji_136@2x.png',
+  '[骷髅]': 'emoji_137@2x.png',
+  '[麦克风]': 'emoji_138@2x.png',
+  '[麻将]': 'emoji_139@2x.png',
+  '[鼓掌]': 'emoji_140@2x.png',
+  '[龇牙]': 'emoji_141@2x.png',
+};
+export const emojiName:Array<string> = [
+  '[龇牙]',
+  '[调皮]',
+  '[流汗]',
+  '[偷笑]',
+  '[再见]',
+  '[敲打]',
+  '[擦汗]',
+  '[猪头]',
+  '[玫瑰]',
+  '[流泪]',
+  '[大哭]',
+  '[嘘]',
+  '[酷]',
+  '[抓狂]',
+  '[委屈]',
+  '[便便]',
+  '[炸弹]',
+  '[菜刀]',
+  '[可爱]',
+  '[色]',
+  '[害羞]',
+  '[得意]',
+  '[吐]',
+  '[微笑]',
+  '[怒]',
+  '[尴尬]',
+  '[惊恐]',
+  '[冷汗]',
+  '[爱心]',
+  '[示爱]',
+  '[白眼]',
+  '[傲慢]',
+  '[难过]',
+  '[惊讶]',
+  '[疑问]',
+  '[困]',
+  '[么么哒]',
+  '[憨笑]',
+  '[爱情]',
+  '[衰]',
+  '[撇嘴]',
+  '[阴险]',
+  '[奋斗]',
+  '[发呆]',
+  '[右哼哼]',
+  '[抱抱]',
+  '[坏笑]',
+  '[飞吻]',
+  '[鄙视]',
+  '[晕]',
+  '[大兵]',
+  '[可怜]',
+  '[强]',
+  '[弱]',
+  '[握手]',
+  '[胜利]',
+  '[抱拳]',
+  '[凋谢]',
+  '[米饭]',
+  '[蛋糕]',
+  '[西瓜]',
+  '[啤酒]',
+  '[瓢虫]',
+  '[勾引]',
+  '[OK]',
+  '[爱你]',
+  '[咖啡]',
+  '[月亮]',
+  '[刀]',
+  '[发抖]',
+  '[差劲]',
+  '[拳头]',
+  '[心碎了]',
+  '[太阳]',
+  '[礼物]',
+  '[皮球]',
+  '[骷髅]',
+  '[挥手]',
+  '[闪电]',
+  '[饥饿]',
+  '[咒骂]',
+  '[折磨]',
+  '[抠鼻]',
+  '[鼓掌]',
+  '[糗大了]',
+  '[左哼哼]',
+  '[打哈欠]',
+  '[快哭了]',
+  '[吓]',
+  '[篮球]',
+  '[乒乓]',
+  '[NO]',
+  '[跳跳]',
+  '[怄火]',
+  '[转圈]',
+  '[磕头]',
+  '[回头]',
+  '[跳绳]',
+  '[激动]',
+  '[街舞]',
+  '[献吻]',
+  '[左太极]',
+  '[右太极]',
+  '[闭嘴]',
+  '[猫咪]',
+  '[红双喜]',
+  '[鞭炮]',
+  '[红灯笼]',
+  '[麻将]',
+  '[麦克风]',
+  '[礼品袋]',
+  '[信封]',
+  '[象棋]',
+  '[彩带]',
+  '[蜡烛]',
+  '[爆筋]',
+  '[棒棒糖]',
+  '[奶瓶]',
+  '[面条]',
+  '[香蕉]',
+  '[飞机]',
+  '[左车头]',
+  '[车厢]',
+  '[右车头]',
+  '[多云]',
+  '[下雨]',
+  '[钞票]',
+  '[熊猫]',
+  '[灯泡]',
+  '[风车]',
+  '[闹钟]',
+  '[雨伞]',
+  '[彩球]',
+  '[钻戒]',
+  '[沙发]',
+  '[纸巾]',
+  '[手枪]',
+  '[青蛙]',
+];
+export const faceUrl = 'https://web.sdk.qcloud.com/im/assets/face-elem/';
+
+export const bigEmojiList:Array<any> = [
+  {
+    icon: 1,
+    list: ['yz00', 'yz01', 'yz02', 'yz03', 'yz04', 'yz05', 'yz06', 'yz07', 'yz08',
+      'yz09', 'yz10', 'yz11', 'yz12', 'yz13', 'yz14', 'yz15', 'yz16', 'yz17'],
+  },
+  {
+    icon: 2,
+    list: ['ys00', 'ys01', 'ys02', 'ys03', 'ys04', 'ys05', 'ys06', 'ys07', 'ys08',
+      'ys09', 'ys10', 'ys11', 'ys12', 'ys13', 'ys14', 'ys15'],
+  },
+  {
+    icon: 3,
+    list: ['gcs00', 'gcs01', 'gcs02', 'gcs03', 'gcs04', 'gcs05', 'gcs06', 'gcs07',
+      'gcs08', 'gcs09', 'gcs10', 'gcs11', 'gcs12', 'gcs13', 'gcs14', 'gcs15', 'gcs16'],
+  },
+];

Some files were not shown because too many files changed in this diff