潘超林 hai 6 días
pai
achega
2c72a4397b
Modificáronse 100 ficheiros con 3106 adicións e 5471 borrados
  1. 9 8
      App.vue
  2. 0 24
      TUIKit/CHANGELOG.md
  3. 0 2
      TUIKit/README.md
  4. 0 7
      TUIKit/assets/icon/add-circle.svg
  5. 0 6
      TUIKit/assets/icon/forward-each.svg
  6. 0 8
      TUIKit/assets/icon/forward-merge.svg
  7. 0 5
      TUIKit/assets/icon/multiple-select.svg
  8. 0 6
      TUIKit/assets/icon/radio.svg
  9. 2 2
      TUIKit/assets/styles/sample.scss
  10. 7 8
      TUIKit/components/TUIChat/chat-header/index.vue
  11. 1 51
      TUIKit/components/TUIChat/config.ts
  12. 0 15
      TUIKit/components/TUIChat/emoji-config/custom-emoji.ts
  13. 0 114
      TUIKit/components/TUIChat/emoji-config/default-emoji.ts
  14. 0 140
      TUIKit/components/TUIChat/emoji-config/index.ts
  15. 0 66
      TUIKit/components/TUIChat/emoji-config/locales/en.ts
  16. 0 66
      TUIKit/components/TUIChat/emoji-config/locales/zh_cn.ts
  17. 0 35
      TUIKit/components/TUIChat/entry-chat-only.ts
  18. 59 86
      TUIKit/components/TUIChat/forward/index.vue
  19. 142 173
      TUIKit/components/TUIChat/index.vue
  20. 0 297
      TUIKit/components/TUIChat/indexlink.vue
  21. 14 26
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue
  22. 1 1
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/web.scss
  23. 112 107
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/index.vue
  24. 1 1
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/h5.scss
  25. 1 1
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/web.scss
  26. 3 12
      TUIKit/components/TUIChat/message-input-toolbar/file-upload/index.vue
  27. 14 21
      TUIKit/components/TUIChat/message-input-toolbar/image-upload/index.vue
  28. 63 95
      TUIKit/components/TUIChat/message-input-toolbar/index.vue
  29. 2 0
      TUIKit/components/TUIChat/message-input-toolbar/otgoing-call/index.ts
  30. 117 0
      TUIKit/components/TUIChat/message-input-toolbar/otgoing-call/index.vue
  31. 0 6
      TUIKit/components/TUIChat/message-input-toolbar/style/uni.scss
  32. 2 2
      TUIKit/components/TUIChat/message-input-toolbar/toolbar-item-container/index.vue
  33. 5 5
      TUIKit/components/TUIChat/message-input-toolbar/user-selector/index.vue
  34. 19 26
      TUIKit/components/TUIChat/message-input-toolbar/video-upload/index.vue
  35. 15 14
      TUIKit/components/TUIChat/message-input-toolbar/words/index.vue
  36. 97 72
      TUIKit/components/TUIChat/message-input/index.vue
  37. 44 30
      TUIKit/components/TUIChat/message-input/message-input-at/index.vue
  38. 25 26
      TUIKit/components/TUIChat/message-input/message-input-audio.vue
  39. 1 10
      TUIKit/components/TUIChat/message-input/message-input-button.vue
  40. 72 78
      TUIKit/components/TUIChat/message-input/message-input-editor.vue
  41. 16 7
      TUIKit/components/TUIChat/message-input/message-input-quote/index.vue
  42. 483 480
      TUIKit/components/TUIChat/message-list/index.vue
  43. 297 0
      TUIKit/components/TUIChat/message-list/interview-application.vue
  44. 4 4
      TUIKit/components/TUIChat/message-list/message-elements/message-audio.vue
  45. 195 419
      TUIKit/components/TUIChat/message-list/message-elements/message-bubble.vue
  46. 139 207
      TUIKit/components/TUIChat/message-list/message-elements/message-custom.vue
  47. 16 17
      TUIKit/components/TUIChat/message-list/message-elements/message-face.vue
  48. 16 39
      TUIKit/components/TUIChat/message-list/message-elements/message-file.vue
  49. 3 0
      TUIKit/components/TUIChat/message-list/message-elements/message-image.vue
  50. 16 92
      TUIKit/components/TUIChat/message-list/message-elements/message-quote/index.vue
  51. 0 47
      TUIKit/components/TUIChat/message-list/message-elements/message-quote/interface.ts
  52. 0 137
      TUIKit/components/TUIChat/message-list/message-elements/message-record/index.vue
  53. 2 28
      TUIKit/components/TUIChat/message-list/message-elements/message-text.vue
  54. 66 2
      TUIKit/components/TUIChat/message-list/message-elements/message-timestamp.vue
  55. 1 1
      TUIKit/components/TUIChat/message-list/message-elements/message-video.vue
  56. 52 36
      TUIKit/components/TUIChat/message-list/message-elements/read-status/index.vue
  57. 0 434
      TUIKit/components/TUIChat/message-list/message-elements/simple-message-list/index.vue
  58. 0 102
      TUIKit/components/TUIChat/message-list/message-elements/simple-message-list/message-container.vue
  59. 86 66
      TUIKit/components/TUIChat/message-list/message-group-application/index.vue
  60. 25 68
      TUIKit/components/TUIChat/message-list/message-tool/index.vue
  61. 1 1
      TUIKit/components/TUIChat/message-list/message-tool/message-revoked.vue
  62. 78 54
      TUIKit/components/TUIChat/message-list/read-receipt-panel/index.vue
  63. 58 32
      TUIKit/components/TUIChat/message-list/scroll-button/index.vue
  64. 1 1
      TUIKit/components/TUIChat/message-list/style/color.scss
  65. 0 7
      TUIKit/components/TUIChat/message-list/style/index.scss
  66. 9 7
      TUIKit/components/TUIChat/message-list/style/web.scss
  67. 0 134
      TUIKit/components/TUIChat/mulitple-select-panel/index.vue
  68. 0 17
      TUIKit/components/TUIChat/offlinePushInfoManager/const.ts
  69. 0 6
      TUIKit/components/TUIChat/offlinePushInfoManager/index.ts
  70. 0 21
      TUIKit/components/TUIChat/offlinePushInfoManager/info.ts
  71. 0 49
      TUIKit/components/TUIChat/offlinePushInfoManager/interface.ts
  72. 0 76
      TUIKit/components/TUIChat/offlinePushInfoManager/offlinePushInfoManager.ts
  73. 7 7
      TUIKit/components/TUIChat/server.ts
  74. 1 1
      TUIKit/components/TUIChat/style/h5.scss
  75. 0 7
      TUIKit/components/TUIChat/style/index.scss
  76. 0 12
      TUIKit/components/TUIChat/style/web.scss
  77. 0 70
      TUIKit/components/TUIChat/utils/chatStorage.ts
  78. 0 86
      TUIKit/components/TUIChat/utils/conversationDraft.ts
  79. 215 0
      TUIKit/components/TUIChat/utils/emoji-config/index.ts
  80. 66 0
      TUIKit/components/TUIChat/utils/emojiList.ts
  81. 17 30
      TUIKit/components/TUIChat/utils/sendMessage.ts
  82. 50 58
      TUIKit/components/TUIChat/utils/utils.ts
  83. 3 21
      TUIKit/components/TUIChat/utils/wordsList.ts
  84. 4 4
      TUIKit/components/TUIContact/contact-info/contact-info-config.ts
  85. 0 363
      TUIKit/components/TUIContact/contact-list/index copy.vue
  86. 132 212
      TUIKit/components/TUIContact/contact-list/index.vue
  87. 2 20
      TUIKit/components/TUIContact/contact-list/style/web.scss
  88. 2 2
      TUIKit/components/TUIContact/contact-search/index.vue
  89. 7 16
      TUIKit/components/TUIContact/index.vue
  90. 38 24
      TUIKit/components/TUIContact/utils/index.ts
  91. 5 5
      TUIKit/components/TUIConversation/actions-menu/index.vue
  92. 4 4
      TUIKit/components/TUIConversation/conversation-header/server.ts
  93. BIN=BIN
      TUIKit/components/TUIConversation/conversation-list/empaty.jpg
  94. 98 52
      TUIKit/components/TUIConversation/conversation-list/index.vue
  95. 1 1
      TUIKit/components/TUIConversation/conversation-list/style/color.scss
  96. 10 0
      TUIKit/components/TUIConversation/conversation-list/style/h5.scss
  97. 9 28
      TUIKit/components/TUIConversation/conversation-list/style/web.scss
  98. 0 2
      TUIKit/components/TUIConversation/entry.ts
  99. 43 203
      TUIKit/components/TUIConversation/index.vue
  100. 0 0
      TUIKit/components/TUIConversation/merChantSideIndex.vue

+ 9 - 8
App.vue

@@ -48,14 +48,11 @@ TUITranslateService.useI18n();
 // #endif
 
 TUIChatKit.init();
-const SDKAppID = 1600005199; // 测试版
-// const SDKAppID = 1600042286; // 正式
-
-
-const secretKey = "82f8b45664eb3252cf02f939aaac43cd81ba25b7c7f94f45be4f05e173e1ab2d"; // 测试key
-// const secretKey = "acecc3c3e0a1a83c7b8fa383467bd658c8bb1297c1f2f1f15088add915e68504"; // Your 正式key
+const SDKAppID = 1600037650; // 测试appid
+// const secretKey =
+//   "2f6c61ac073596b399d7605460e3dc3aba5edec16c2b8196a33464cc3caaa1f7"; // 测试key
 uni.$chat_SDKAppID = SDKAppID;
-uni.$chat_secretKey = secretKey;
+// uni.$chat_secretKey = secretKey;
 
 export default {
   onLaunch: function () {
@@ -95,8 +92,12 @@ export default {
   onHide: function () {},
 };
 </script>
-<style>
+<style lang="scss">
+$main-color: #12ae85;
 /* 每个页面公共css */
+@import "@/uni_modules/uview-ui/index.scss";
+@import "./static/css/common.scss";
+@import "./static/css/qzy.scss";
 uni-page-body,
 html,
 body,

+ 0 - 24
TUIKit/CHANGELOG.md

@@ -1,27 +1,3 @@
-## [2.2.3] (2024-07-05)
-
-### Features
-- 支持会话草稿
-- 语音消息使用红点提示是否播放
-
-### Fix
-- 修复邀请加入群聊选人组件未过滤已有群成员问题
-- 修复 TUISearch 搜索文本消息出现 [系统消息] 前缀问题
-- 修复 TUIGroup 中 group-profile\group-name在 vue3 环境下 UI 崩坏的问题
-- 修复 Overlay 在构建微信小程序时点击事件无法触发的问题
-- 修复消息引用语音但展示为"聊天记录"的问题
-- 修复 Callkit 在 H5 环境下布局只有半屏的问题
-
-## [2.2.0] (2024-06-17)
-
-### Features
-- 支持消息多选、消息逐条转发、消息合并转发
-- 优化 TUIKit 集成流程
-- 支持表情包自定义
-- uniapp callkit 支持 offlinePushInfo
-- uniapp callkit 支持自定义铃音
-- 被引用消息撤回时隐藏原始消息内容并提醒已撤回
-
 ## [2.1.3] (2024-05-17)
 
 ### Features

+ 0 - 2
TUIKit/README.md

@@ -4,8 +4,6 @@ chat-uikit-uniapp (vue2 / vue3)是基于腾讯云 Chat SDK 的一款 uniapp
 chat-uikit-uniapp 界面效果如下图所示:
 ![](https://qcloudimg.tencent-cloud.cn/raw/2f16b1be0591a325250f9066af898036.png)
 
-> 为尊重表情设计版权,IM Demo/TUIKit 工程中不包含大表情元素切图,正式上线商用前请您替换为自己设计或拥有版权的其他表情包。默认的小黄脸表情包版权归腾讯云所有,可有偿授权使用,如您希望获得授权可 [提交工单](https://console.cloud.tencent.com/workorder/category?level1_id=29&level2_id=40&source=14&data_title=%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%20IM&step=1) 联系我们。
-
 ## chat-uikit-uniapp 支持平台
 
 - Android

+ 0 - 7
TUIKit/assets/icon/add-circle.svg

@@ -1,7 +0,0 @@
-<svg width="56" height="57" viewBox="0 0 56 57" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="&#231;&#188;&#150;&#231;&#187;&#132; 4">
-<circle id="&#230;&#164;&#173;&#229;&#156;&#134;&#229;&#189;&#162;&#229;&#164;&#135;&#228;&#187;&#189; 27" cx="28" cy="28.8535" r="26" stroke="#444444" stroke-width="4"/>
-<rect id="&#231;&#159;&#169;&#229;&#189;&#162;&#229;&#164;&#135;&#228;&#187;&#189; 8" x="15" y="27.8535" width="26" height="4" rx="1" fill="#444444"/>
-<rect id="&#231;&#159;&#169;&#229;&#189;&#162;&#229;&#164;&#135;&#228;&#187;&#189; 8_2" x="26" y="42.8535" width="26" height="4" rx="1" transform="rotate(-90 26 42.8535)" fill="#444444"/>
-</g>
-</svg>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 6
TUIKit/assets/icon/forward-each.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 8
TUIKit/assets/icon/forward-merge.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 5
TUIKit/assets/icon/multiple-select.svg


+ 0 - 6
TUIKit/assets/icon/radio.svg

@@ -1,6 +0,0 @@
-<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="&#231;&#188;&#150;&#231;&#187;&#132;">
-<circle id="&#230;&#164;&#173;&#229;&#156;&#134;&#229;&#189;&#162;" cx="22" cy="22" r="22" fill="#147AFF"/>
-<path id="&#232;&#183;&#175;&#229;&#190;&#132;" d="M29.8629 13.8987C30.2582 13.5129 30.8913 13.5206 31.277 13.9159L33.035 15.7172C33.4207 16.1124 33.4131 16.7454 33.0179 17.1312L19.0302 30.7857C18.6448 31.162 18.0305 31.1653 17.641 30.7933L10.9897 24.4405C10.5904 24.0591 10.5758 23.4262 10.9571 23.0268L12.6958 21.2055C13.0772 20.8061 13.7102 20.7914 14.1096 21.1727L18.3055 25.1785L29.8629 13.8987Z" fill="white"/>
-</g>
-</svg>

+ 2 - 2
TUIKit/assets/styles/sample.scss

@@ -10,7 +10,7 @@
     overflow: hidden;
     .TUIKit-navbar-item {
       padding: 10px;
-      color: #147aff;
+      color: #00b693;
       font-weight: 500;
       cursor: pointer;
     }
@@ -33,7 +33,7 @@
       .TUIKit-main-main {
         flex: 1;
         display: flex;
-        .chat{
+        .chat {
           flex: 1;
         }
         .chat-aside {

+ 7 - 8
TUIKit/components/TUIChat/chat-header/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <view style="display: none" />
+  <view style="display: none;" />
 </template>
 
 <script setup lang="ts">
@@ -8,18 +8,17 @@ import {
   StoreName,
   IConversationModel,
   TUITranslateService,
-} from "@tencentcloud/chat-uikit-engine";
-import { TUIGlobal } from "@tencentcloud/universal-api";
-import { onLoad } from "@dcloudio/uni-app";
-import { onMounted, onUnmounted, ref } from "../../../adapter-vue";
+} from '@tencentcloud/chat-uikit-engine';
+import { TUIGlobal } from '@tencentcloud/universal-api';
+import { onLoad } from '@dcloudio/uni-app';
+import { onMounted, onUnmounted, ref } from '../../../adapter-vue';
 
 const currentConversation = ref<IConversationModel>();
 const typingStatus = ref(false);
 
 const setChatHeaderContent = (content: string) => {
-  console.log("xxxxx", content);
   TUIGlobal?.setNavigationBarTitle({
-    title: content || "云通信 IM",
+    title: content || '云通信 IM',
   });
 };
 
@@ -55,7 +54,7 @@ function onCurrentConversationUpdated(conversation: IConversationModel) {
 function onTypingStatusUpdated(status: boolean) {
   typingStatus.value = status;
   if (typingStatus.value) {
-    setChatHeaderContent(TUITranslateService.t("TUIChat.对方正在输入"));
+    setChatHeaderContent(TUITranslateService.t('TUIChat.对方正在输入'));
   } else {
     setChatHeaderContent(currentConversation.value?.getShowName());
   }

+ 1 - 51
TUIKit/components/TUIChat/config.ts

@@ -1,33 +1,8 @@
 class TUIChatConfig {
   static instance: TUIChatConfig;
   private chatType: string;
-  private features: Record<string, any>;
   constructor() {
     this.chatType = '';
-    this.features = {
-      DownloadFile: true,
-      CopyMessage: true,
-      DeleteMessage: true,
-      RevokeMessage: true,
-      modifyMessage: true,
-      QuoteMessage: true,
-      ForwardMessage: true,
-      TranslateMessage: true,
-      VoiceToText: true,
-      MultiSelection: true,
-      EmojiReaction: true,
-      InputEmoji: true,
-      InputStickers: true,
-      InputImage: true,
-      InputVoice: true,
-      InputVideo: true,
-      InputFile: true,
-      InputEvaluation: true,
-      InputQuickReplies: true,
-      InputMention: true,
-      MessageSearch: true,
-      ReadStatus: true,
-    };
   }
 
   static getInstance(): TUIChatConfig {
@@ -44,31 +19,6 @@ class TUIChatConfig {
   getChatType() {
     return this.chatType;
   }
-
-  hideTUIChatFeatures(features: string[]) {
-    if (!features) {
-      return;
-    }
-    features.forEach((feature: string) => {
-      if (this.features[feature]) {
-        this.features[feature] = false;
-      }
-    });
-  }
-
-  getFeatureConfig(key?: string) {
-    if (key) {
-      return this.features[key];
-    }
-    return this.features;
-  }
 }
 
-const ChatConfig = TUIChatConfig.getInstance();
-const hideTUIChatFeatures = ChatConfig.hideTUIChatFeatures.bind(ChatConfig);
-
-export {
-  hideTUIChatFeatures,
-};
-
-export default ChatConfig;
+export default TUIChatConfig.getInstance();

+ 0 - 15
TUIKit/components/TUIChat/emoji-config/custom-emoji.ts

@@ -1,15 +0,0 @@
-import { IEmojiGroupList } from '../../../interface';
-
-/**
-* Custom big emoji
-*/
-export const CUSTOM_BIG_EMOJI_URL: string = '';
-
-export const CUSTOM_BIG_EMOJI_GROUP_LIST: IEmojiGroupList = [];
-
-/**
-* Custom basic emoji
-*/
-export const CUSTOM_BASIC_EMOJI_URL: string = '';
-
-export const CUSTOM_BASIC_EMOJI_URL_MAPPING: Record<string, string> = {};

+ 0 - 114
TUIKit/components/TUIChat/emoji-config/default-emoji.ts

@@ -1,114 +0,0 @@
-/**
- * Emoji input interface in the chat screen.
- * In respect for the copyright of the emoji design, the Chat Demo/TUIKit project does not include the cutouts of large emoji elements.
- * Please replace them with your own designed or copyrighted emoji packs before the official launch for commercial use.
- * The default small yellow face emoji pack is copyrighted by Tencent Cloud and can be authorized for a fee.
- * If you wish to obtain authorization, please submit a ticket to contact us.
- *
- * submit a ticket url:https://console.tencentcloud.com/workorder/category?level1_id=29&level2_id=40&source=14&data_title=Chat&step=1
- */
-import { default as emojiCNLocales } from './locales/zh_cn';
-import { default as emojiENLocales } from './locales/en';
-import { EMOJI_TYPE } from '../../../constant';
-import { IEmojiGroupList } from '../../../interface';
-
-export const DEFAULT_BASIC_EMOJI_URL = 'https://web.sdk.qcloud.com/im/assets/emoji-plugin/';
-export const DEFAULT_BIG_EMOJI_URL = 'https://web.sdk.qcloud.com/im/assets/face-elem/';
-
-export const DEFAULT_BASIC_EMOJI_URL_MAPPING: Record<string, string> = {
-  '[TUIEmoji_Expect]': 'emoji_0@2x.png',
-  '[TUIEmoji_Blink]': 'emoji_1@2x.png',
-  '[TUIEmoji_Guffaw]': 'emoji_2@2x.png',
-  '[TUIEmoji_KindSmile]': 'emoji_3@2x.png',
-  '[TUIEmoji_Haha]': 'emoji_4@2x.png',
-  '[TUIEmoji_Cheerful]': 'emoji_5@2x.png',
-  '[TUIEmoji_Smile]': 'emoji_6@2x.png',
-  '[TUIEmoji_Sorrow]': 'emoji_7@2x.png',
-  '[TUIEmoji_Speechless]': 'emoji_8@2x.png',
-  '[TUIEmoji_Amazed]': 'emoji_9@2x.png',
-  '[TUIEmoji_Complacent]': 'emoji_10@2x.png',
-  '[TUIEmoji_Lustful]': 'emoji_11@2x.png',
-  '[TUIEmoji_Stareyes]': 'emoji_12@2x.png',
-  '[TUIEmoji_Giggle]': 'emoji_13@2x.png',
-  '[TUIEmoji_Daemon]': 'emoji_14@2x.png',
-  '[TUIEmoji_Rage]': 'emoji_15@2x.png',
-  '[TUIEmoji_Yawn]': 'emoji_16@2x.png',
-  '[TUIEmoji_TearsLaugh]': 'emoji_17@2x.png',
-  '[TUIEmoji_Silly]': 'emoji_18@2x.png',
-  '[TUIEmoji_Wail]': 'emoji_19@2x.png',
-  '[TUIEmoji_Kiss]': 'emoji_20@2x.png',
-  '[TUIEmoji_Trapped]': 'emoji_21@2x.png',
-  '[TUIEmoji_Fear]': 'emoji_22@2x.png',
-  '[TUIEmoji_BareTeeth]': 'emoji_23@2x.png',
-  '[TUIEmoji_FlareUp]': 'emoji_24@2x.png',
-  '[TUIEmoji_Tact]': 'emoji_25@2x.png',
-  '[TUIEmoji_Shit]': 'emoji_26@2x.png',
-  '[TUIEmoji_ShutUp]': 'emoji_27@2x.png',
-  '[TUIEmoji_Sigh]': 'emoji_28@2x.png',
-  '[TUIEmoji_Hehe]': 'emoji_29@2x.png',
-  '[TUIEmoji_Silent]': 'emoji_30@2x.png',
-  '[TUIEmoji_Skull]': 'emoji_31@2x.png',
-  '[TUIEmoji_Mask]': 'emoji_32@2x.png',
-  '[TUIEmoji_Beer]': 'emoji_33@2x.png',
-  '[TUIEmoji_Cake]': 'emoji_34@2x.png',
-  '[TUIEmoji_RedPacket]': 'emoji_35@2x.png',
-  '[TUIEmoji_Bombs]': 'emoji_36@2x.png',
-  '[TUIEmoji_Ai]': 'emoji_37@2x.png',
-  '[TUIEmoji_Celebrate]': 'emoji_38@2x.png',
-  '[TUIEmoji_Bless]': 'emoji_39@2x.png',
-  '[TUIEmoji_Flower]': 'emoji_40@2x.png',
-  '[TUIEmoji_Watermelon]': 'emoji_41@2x.png',
-  '[TUIEmoji_Cow]': 'emoji_42@2x.png',
-  '[TUIEmoji_Fool]': 'emoji_43@2x.png',
-  '[TUIEmoji_Surprised]': 'emoji_44@2x.png',
-  '[TUIEmoji_Askance]': 'emoji_45@2x.png',
-  '[TUIEmoji_Monster]': 'emoji_46@2x.png',
-  '[TUIEmoji_Pig]': 'emoji_47@2x.png',
-  '[TUIEmoji_Coffee]': 'emoji_48@2x.png',
-  '[TUIEmoji_Ok]': 'emoji_49@2x.png',
-  '[TUIEmoji_Heart]': 'emoji_50@2x.png',
-  '[TUIEmoji_Sun]': 'emoji_51@2x.png',
-  '[TUIEmoji_Moon]': 'emoji_52@2x.png',
-  '[TUIEmoji_Star]': 'emoji_53@2x.png',
-  '[TUIEmoji_Rich]': 'emoji_54@2x.png',
-  '[TUIEmoji_Fortune]': 'emoji_55@2x.png',
-  '[TUIEmoji_857]': 'emoji_56@2x.png',
-  '[TUIEmoji_666]': 'emoji_57@2x.png',
-  '[TUIEmoji_Prohibit]': 'emoji_58@2x.png',
-  '[TUIEmoji_Convinced]': 'emoji_59@2x.png',
-  '[TUIEmoji_Knife]': 'emoji_60@2x.png',
-  '[TUIEmoji_Like]': 'emoji_61@2x.png',
-};
-
-export const BIG_EMOJI_GROUP_LIST: IEmojiGroupList = [
-  {
-    emojiGroupID: 1,
-    type: EMOJI_TYPE.BIG,
-    url: DEFAULT_BIG_EMOJI_URL,
-    list: ['yz00', 'yz01', 'yz02', 'yz03', 'yz04', 'yz05', 'yz06', 'yz07', 'yz08',
-      'yz09', 'yz10', 'yz11', 'yz12', 'yz13', 'yz14', 'yz15', 'yz16', 'yz17'],
-  },
-  {
-    emojiGroupID: 2,
-    type: EMOJI_TYPE.BIG,
-    url: DEFAULT_BIG_EMOJI_URL,
-    list: ['ys00', 'ys01', 'ys02', 'ys03', 'ys04', 'ys05', 'ys06', 'ys07', 'ys08',
-      'ys09', 'ys10', 'ys11', 'ys12', 'ys13', 'ys14', 'ys15'],
-  },
-  {
-    emojiGroupID: 3,
-    type: EMOJI_TYPE.BIG,
-    url: DEFAULT_BIG_EMOJI_URL,
-    list: ['gcs00', 'gcs01', 'gcs02', 'gcs03', 'gcs04', 'gcs05', 'gcs06', 'gcs07',
-      'gcs08', 'gcs09', 'gcs10', 'gcs11', 'gcs12', 'gcs13', 'gcs14', 'gcs15', 'gcs16'],
-  },
-];
-
-export const BASIC_EMOJI_NAME_TO_KEY_MAPPING = {
-  ...Object.fromEntries(
-    Object.entries(emojiCNLocales)?.map(([key, val]) => [val, key]),
-  ),
-  ...Object.fromEntries(
-    Object.entries(emojiENLocales)?.map(([key, val]) => [val, key]),
-  ),
-};

+ 0 - 140
TUIKit/components/TUIChat/emoji-config/index.ts

@@ -1,140 +0,0 @@
-import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
-import { CUSTOM_BASIC_EMOJI_URL, CUSTOM_BIG_EMOJI_URL, CUSTOM_BASIC_EMOJI_URL_MAPPING, CUSTOM_BIG_EMOJI_GROUP_LIST } from './custom-emoji';
-import { DEFAULT_BASIC_EMOJI_URL, BIG_EMOJI_GROUP_LIST, DEFAULT_BASIC_EMOJI_URL_MAPPING, BASIC_EMOJI_NAME_TO_KEY_MAPPING, DEFAULT_BIG_EMOJI_URL } from './default-emoji';
-import { default as emojiCNLocales } from './locales/zh_cn';
-import { IEmojiGroupList } from '../../../interface';
-import { EMOJI_TYPE } from '../../../constant';
-import { isWeChat } from '../../../utils/env';
-
-const hasCustomBasicEmoji = CUSTOM_BASIC_EMOJI_URL && Object.keys(CUSTOM_BASIC_EMOJI_URL_MAPPING).length;
-
-const BASIC_EMOJI_URL = hasCustomBasicEmoji ? CUSTOM_BASIC_EMOJI_URL : DEFAULT_BASIC_EMOJI_URL;
-
-const BASIC_EMOJI_URL_MAPPING = hasCustomBasicEmoji ? CUSTOM_BASIC_EMOJI_URL_MAPPING : DEFAULT_BASIC_EMOJI_URL_MAPPING;
-
-const EMOJI_GROUP_LIST: IEmojiGroupList = [
-  {
-    emojiGroupID: 0,
-    type: EMOJI_TYPE.BASIC,
-    url: BASIC_EMOJI_URL,
-    list: Object.keys(BASIC_EMOJI_URL_MAPPING),
-  },
-  ...BIG_EMOJI_GROUP_LIST,
-  ...CUSTOM_BIG_EMOJI_GROUP_LIST,
-];
-
-/**
- * Converts a basic emoji key into its corresponding name.
- * Example:
- * '[Smile]' => '[TUIEmoji_Smile]'
- * @param {string} key - The emoji key.
- * @return {string} The corresponding emoji name.
- */
-const convertKeyToEmojiName = (key: string): string => {
-  // WeChat does not support emoji translation
-  return isWeChat ? emojiCNLocales[key] : TUITranslateService.t(`Emoji.${key}`);
-};
-
-/**
- * Transforms a text containing emoji keys into a text with Chinese or English basic emoji names
- * Example:
- * 'hello[TUIEmoji_Smile]!' => 'hello[Smile]!''
- * @param {string} text - The text containing emoji keys.
- * @return {string} The transformed text with emoji keys replaced by emoji names.
- */
-const transformTextWithKeysToEmojiNames = (text: string): string => {
-  if (!text) {
-    return '';
-  }
-  const reg = /(\[.+?\])/g;
-  let txt: string = text;
-  if (reg.test(text)) {
-    txt = text.replace(reg, match => BASIC_EMOJI_URL_MAPPING[match] ? convertKeyToEmojiName(match) : match);
-  }
-  return txt;
-};
-
-/**
- * Transforms a text containing Chinese or English basic emoji names into a text with emoji keys.
- * Example:
- * 'hello[Smile]!' => 'hello[TUIEmoji_Smile]!'
- * @param {string} text - The text containing emoji names.
- * @return {string} The transformed text with emoji names replaced by emoji keys.
- */
-const transformTextWithEmojiNamesToKeys = (text: string) => {
-  if (!text) {
-    return '';
-  }
-  const reg = /(\[.+?\])/g;
-  let txt: string = text;
-  if (reg.test(text)) {
-    txt = text.replace(reg, match => BASIC_EMOJI_NAME_TO_KEY_MAPPING[match] || match);
-  }
-  return txt;
-};
-
-/**
-* The configuration aims to provide compatibility with versions prior to 2.2.0
-*/
-const emojiConfig = {
-  emojiBaseUrl: BASIC_EMOJI_URL,
-  emojiUrlMapping: BASIC_EMOJI_URL_MAPPING,
-  emojiNameMapping: {
-    ...emojiCNLocales,
-  },
-};
-
-/**
- * Transform text message to renderable array contains image and text.
- * Example: hello[TUIEmoji_Smile], I am happy.
- * -> [{type: 'text', content: 'hello'}, {type: 'image', content: 'https://.../smile.png'}, {type: 'text', content: ', I am happy.'}]
- * @param text
- * @returns Array<{ type: 'text' | 'image'; content: string; emojiKey?: string; }>
- */
-const parseTextToRenderArray = (text: string): Array<{ type: 'text' | 'image'; content: string; emojiKey?: string }> => {
-  const emojiRegex = /\[([^\]]+)\]/g;
-  const result: any[] = [];
-
-  let match: RegExpExecArray | null;
-  let lastIndex = 0;
-
-  while ((match = emojiRegex.exec(text)) !== null) {
-    const startIndex = match.index;
-    const endIndex = emojiRegex.lastIndex;
-    const emojiKey = match[0];
-
-    if (startIndex > lastIndex) {
-      result.push({ type: 'text', content: text.substring(lastIndex, startIndex) });
-    }
-
-    const emojiUrl = BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[emojiKey];
-    if (emojiUrl) {
-      result.push({ type: 'image', content: emojiUrl, emojiKey });
-    } else {
-      result.push({ type: 'text', content: emojiKey });
-    }
-
-    lastIndex = endIndex;
-    emojiRegex.lastIndex = lastIndex;
-  }
-
-  if (lastIndex < text.length) {
-    result.push({ type: 'text', content: text.substring(lastIndex) });
-  }
-
-  return result;
-};
-
-export {
-  EMOJI_GROUP_LIST,
-  CUSTOM_BIG_EMOJI_URL,
-  DEFAULT_BIG_EMOJI_URL,
-  CUSTOM_BASIC_EMOJI_URL,
-  BASIC_EMOJI_URL_MAPPING,
-  CUSTOM_BASIC_EMOJI_URL_MAPPING,
-  convertKeyToEmojiName,
-  parseTextToRenderArray,
-  transformTextWithKeysToEmojiNames,
-  transformTextWithEmojiNamesToKeys,
-  emojiConfig,
-};

+ 0 - 66
TUIKit/components/TUIChat/emoji-config/locales/en.ts

@@ -1,66 +0,0 @@
-const Emoji = {
-  '[TUIEmoji_Smile]': '[Smile]',
-  '[TUIEmoji_Expect]': '[Expect]',
-  '[TUIEmoji_Blink]': '[Blink]',
-  '[TUIEmoji_Guffaw]': '[Guffaw]',
-  '[TUIEmoji_KindSmile]': '[KindSmile]',
-  '[TUIEmoji_Haha]': '[Haha]',
-  '[TUIEmoji_Cheerful]': '[Cheerful]',
-  '[TUIEmoji_Speechless]': '[Speechless]',
-  '[TUIEmoji_Amazed]': '[Amazed]',
-  '[TUIEmoji_Sorrow]': '[Sorrow]',
-  '[TUIEmoji_Complacent]': '[Complacent]',
-  '[TUIEmoji_Silly]': '[Silly]',
-  '[TUIEmoji_Lustful]': '[Lustful]',
-  '[TUIEmoji_Giggle]': '[Giggle]',
-  '[TUIEmoji_Kiss]': '[Kiss]',
-  '[TUIEmoji_Wail]': '[Wail]',
-  '[TUIEmoji_TearsLaugh]': '[TearsLaugh]',
-  '[TUIEmoji_Trapped]': '[Trapped]',
-  '[TUIEmoji_Mask]': '[Mask]',
-  '[TUIEmoji_Fear]': '[Fear]',
-  '[TUIEmoji_BareTeeth]': '[BareTeeth]',
-  '[TUIEmoji_FlareUp]': '[FlareUp]',
-  '[TUIEmoji_Yawn]': '[Yawn]',
-  '[TUIEmoji_Tact]': '[Tact]',
-  '[TUIEmoji_Stareyes]': '[StarEyes]',
-  '[TUIEmoji_ShutUp]': '[ShutUp]',
-  '[TUIEmoji_Sigh]': '[Sigh]',
-  '[TUIEmoji_Hehe]': '[Hehe]',
-  '[TUIEmoji_Silent]': '[Silent]',
-  '[TUIEmoji_Surprised]': '[Surprised]',
-  '[TUIEmoji_Askance]': '[Askance]]',
-  '[TUIEmoji_Ok]': '[OK]',
-  '[TUIEmoji_Shit]': '[Shit]',
-  '[TUIEmoji_Monster]': '[Monster]',
-  '[TUIEmoji_Daemon]': '[Daemon]',
-  '[TUIEmoji_Rage]': '[Rage]',
-  '[TUIEmoji_Fool]': '[Fool]',
-  '[TUIEmoji_Pig]': '[Pig]',
-  '[TUIEmoji_Cow]': '[Cow]',
-  '[TUIEmoji_Ai]': '[AI]',
-  '[TUIEmoji_Skull]': '[Skull]',
-  '[TUIEmoji_Bombs]': '[Bombs]',
-  '[TUIEmoji_Coffee]': '[Coffee]',
-  '[TUIEmoji_Cake]': '[Cake]',
-  '[TUIEmoji_Beer]': '[Beer]',
-  '[TUIEmoji_Flower]': '[Flower]',
-  '[TUIEmoji_Watermelon]': '[Watermelon]',
-  '[TUIEmoji_Rich]': '[Rich]',
-  '[TUIEmoji_Heart]': '[Heart]',
-  '[TUIEmoji_Moon]': '[Moon]',
-  '[TUIEmoji_Sun]': '[Sun]',
-  '[TUIEmoji_Star]': '[Star]',
-  '[TUIEmoji_RedPacket]': '[RedPacket]',
-  '[TUIEmoji_Celebrate]': '[Celebrate]',
-  '[TUIEmoji_Bless]': '[Bless]',
-  '[TUIEmoji_Fortune]': '[Fortune]',
-  '[TUIEmoji_Convinced]': '[Convinced]',
-  '[TUIEmoji_Prohibit]': '[Prohibit]',
-  '[TUIEmoji_666]': '[666]',
-  '[TUIEmoji_857]': '[857]',
-  '[TUIEmoji_Knife]': '[Knife]',
-  '[TUIEmoji_Like]': '[Like]',
-};
-
-export default Emoji;

+ 0 - 66
TUIKit/components/TUIChat/emoji-config/locales/zh_cn.ts

@@ -1,66 +0,0 @@
-const Emoji: Record<string, string> = {
-  '[TUIEmoji_Smile]': '[微笑]',
-  '[TUIEmoji_Expect]': '[期待]',
-  '[TUIEmoji_Blink]': '[眨眼]',
-  '[TUIEmoji_Guffaw]': '[大笑]',
-  '[TUIEmoji_KindSmile]': '[姨母笑]',
-  '[TUIEmoji_Haha]': '[哈哈哈]',
-  '[TUIEmoji_Cheerful]': '[愉快]',
-  '[TUIEmoji_Speechless]': '[无语]',
-  '[TUIEmoji_Amazed]': '[惊讶]',
-  '[TUIEmoji_Sorrow]': '[悲伤]',
-  '[TUIEmoji_Complacent]': '[得意]',
-  '[TUIEmoji_Silly]': '[傻了]',
-  '[TUIEmoji_Lustful]': '[色]',
-  '[TUIEmoji_Giggle]': '[憨笑]',
-  '[TUIEmoji_Kiss]': '[亲亲]',
-  '[TUIEmoji_Wail]': '[大哭]',
-  '[TUIEmoji_TearsLaugh]': '[哭笑]',
-  '[TUIEmoji_Trapped]': '[困]',
-  '[TUIEmoji_Mask]': '[口罩]',
-  '[TUIEmoji_Fear]': '[恐惧]',
-  '[TUIEmoji_BareTeeth]': '[龇牙]',
-  '[TUIEmoji_FlareUp]': '[发怒]',
-  '[TUIEmoji_Yawn]': '[打哈欠]',
-  '[TUIEmoji_Tact]': '[机智]',
-  '[TUIEmoji_Stareyes]': '[星星眼]',
-  '[TUIEmoji_ShutUp]': '[闭嘴]',
-  '[TUIEmoji_Sigh]': '[叹气]',
-  '[TUIEmoji_Hehe]': '[呵呵]',
-  '[TUIEmoji_Silent]': '[收声]',
-  '[TUIEmoji_Surprised]': '[惊喜]',
-  '[TUIEmoji_Askance]': '[白眼]',
-  '[TUIEmoji_Ok]': '[OK]',
-  '[TUIEmoji_Shit]': '[便便]',
-  '[TUIEmoji_Monster]': '[怪兽]',
-  '[TUIEmoji_Daemon]': '[恶魔]',
-  '[TUIEmoji_Rage]': '[恶魔怒]',
-  '[TUIEmoji_Fool]': '[衰]',
-  '[TUIEmoji_Pig]': '[猪]',
-  '[TUIEmoji_Cow]': '[牛]',
-  '[TUIEmoji_Ai]': '[AI]',
-  '[TUIEmoji_Skull]': '[骷髅]',
-  '[TUIEmoji_Bombs]': '[炸弹]',
-  '[TUIEmoji_Coffee]': '[咖啡]',
-  '[TUIEmoji_Cake]': '[蛋糕]',
-  '[TUIEmoji_Beer]': '[啤酒]',
-  '[TUIEmoji_Flower]': '[花]',
-  '[TUIEmoji_Watermelon]': '[瓜]',
-  '[TUIEmoji_Rich]': '[壕]',
-  '[TUIEmoji_Heart]': '[爱心]',
-  '[TUIEmoji_Moon]': '[月亮]',
-  '[TUIEmoji_Sun]': '[太阳]',
-  '[TUIEmoji_Star]': '[星星]',
-  '[TUIEmoji_RedPacket]': '[红包]',
-  '[TUIEmoji_Celebrate]': '[庆祝]',
-  '[TUIEmoji_Bless]': '[福]',
-  '[TUIEmoji_Fortune]': '[发]',
-  '[TUIEmoji_Convinced]': '[服]',
-  '[TUIEmoji_Prohibit]': '[禁]',
-  '[TUIEmoji_666]': '[666]',
-  '[TUIEmoji_857]': '[857]',
-  '[TUIEmoji_Knife]': '[刀]',
-  '[TUIEmoji_Like]': '[赞]',
-};
-
-export default Emoji;

+ 0 - 35
TUIKit/components/TUIChat/entry-chat-only.ts

@@ -1,35 +0,0 @@
-import { TUILogin } from '@tencentcloud/tui-core';
-import { TUIConversationService } from '@tencentcloud/chat-uikit-engine';
-// #ifdef MP-WEIXIN
-import { TUIChatKit } from '../../index.ts';
-// #endif
-
-export const initChat = (options: Record<string, string>) => {
-  // #ifdef MP-WEIXIN
-  // uni-app packages the mini program.
-  // If you call TUIChatKit.init() directly during import, an error will be reported.
-  // You need to init during the page onLoad.
-  TUIChatKit.init();
-  // #endif
-
-  // When opening TUIChat, the options and options.conversationID parameters carried in the url,
-  // determine whether to enter the Chat from the [Conversation List] or [Online Communication].
-  const { chat } = TUILogin.getContext();
-  if (options && options.conversationID && chat?.isReady()) {
-    const { conversationID } = options;
-    // verify conversationID
-    if (!conversationID.startsWith('C2C') && !conversationID.startsWith('GROUP')) {
-      console.warn('conversationID from options is invalid.');
-      return;
-    }
-    // open chat
-    TUIConversationService.switchConversation(conversationID);
-  }
-};
-
-export const logout = (flag: boolean) => {
-  if (flag) {
-    return TUILogin.logout();
-  }
-  return Promise.resolve();
-};

+ 59 - 86
TUIKit/components/TUIChat/forward/index.vue

@@ -10,146 +10,119 @@
       :list="customConversationList"
       :isHiddenBackIcon="isUniFrameWork"
       @cancel="closeForwardPanel"
-      @submit="finishSelected"
+      @submit="onSubmit"
     />
   </Overlay>
 </template>
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref } from '../../../adapter-vue';
-import TUIChatEngine, {
+import {
   TUIStore,
   StoreName,
   TUIChatService,
   TUITranslateService,
+  IConversationModel,
 } from '@tencentcloud/chat-uikit-engine';
 import Overlay from '../../common/Overlay/index.vue';
 import Transfer from '../../common/Transfer/index.vue';
 import { Toast, TOAST_TYPE } from '../../../components/common/Toast';
 import { isUniFrameWork } from '../../../utils/env';
 import { isEnabledMessageReadReceiptGlobal } from '../utils/utils';
-import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../offlinePushInfoManager/index';
-
-interface IEmits {
-  (e: 'toggleMultipleSelectMode', visible?: boolean): void;
-}
-
-const emits = defineEmits<IEmits>();
-
-let selectedToForwardMessageIDList: string[] = [];
-let isMergeForward = false;
+import { createOfflinePushInfo } from '../utils/sendMessage';
 
 const isShowForwardPanel = ref(false);
 const customConversationList = ref();
 
 onMounted(() => {
+  TUIStore.watch(StoreName.CONV, {
+    conversationList: onConversationListUpdated,
+  });
   TUIStore.watch(StoreName.CUSTOM, {
     singleForwardMessageID: onSingleForwardMessageIDUpdated,
-    multipleForwardMessageID: onMultipleForwardMessageIDUpdated,
   });
 });
 
 onUnmounted(() => {
+  // 组件卸载时需要清掉数据 否则小程序会自动打开
+  TUIStore.update(StoreName.CUSTOM, 'singleForwardMessageID', undefined);
+  TUIStore.unwatch(StoreName.CONV, {
+    conversationList: onConversationListUpdated,
+  });
   TUIStore.unwatch(StoreName.CUSTOM, {
     singleForwardMessageID: onSingleForwardMessageIDUpdated,
-    multipleForwardMessageID: onMultipleForwardMessageIDUpdated,
   });
-
-  // tuistore data must be cleared when closing the forward panel
-  clearStoreData();
 });
 
-function onSingleForwardMessageIDUpdated(messageID: string | undefined) {
+function onSingleForwardMessageIDUpdated(messageID: string) {
   if (typeof messageID !== 'undefined') {
-    isMergeForward = false;
-    selectedToForwardMessageIDList = [messageID];
-    openForwardPanel();
-  }
-}
-
-function onMultipleForwardMessageIDUpdated(params: { isMergeForward: boolean; messageIDList: string[] } | undefined) {
-  if (!params) {
-    return;
-  }
-  isMergeForward = false;
-  const {
-    isMergeForward: _isMergeForward,
-    messageIDList: selectedMessageIDList,
-  } = params || {};
-  if (selectedMessageIDList?.length > 0) {
-    isMergeForward = _isMergeForward;
-    selectedToForwardMessageIDList = selectedMessageIDList;
     openForwardPanel();
-  } else {
-    Toast({
-      message: TUITranslateService.t('TUIChat.未选择消息'),
-      type: TOAST_TYPE.ERROR,
-    });
   }
 }
 
-function clearStoreData() {
-  TUIStore.update(StoreName.CUSTOM, 'singleForwardMessageID', undefined);
-  TUIStore.update(StoreName.CUSTOM, 'multipleForwardMessageID', undefined);
-}
-
 function closeForwardPanel(): void {
-  // tuistore data must be cleared when closing the forward panel
-  clearStoreData();
+  // ! 必须通过close函数关闭转发面板 singleForwardMessage必须清掉
+  TUIStore.update(StoreName.CUSTOM, 'singleForwardMessageID', undefined);
   isShowForwardPanel.value = false;
 }
 
 function openForwardPanel(): void {
-  getTransforRenderDataList();
   isShowForwardPanel.value = true;
 }
 
 function finishSelected(selectedConvIDWrapperList: Array<{ userID: string }>): void {
-  if (selectedConvIDWrapperList?.length === 0) return;
-  // to reuse Transfer, so we have to get conversationModel by userID instead of ConversationID
-  const selectedConversationList = selectedConvIDWrapperList.map(IDWrapper => TUIStore.getConversationModel(IDWrapper.userID));
-  const unsentMessageQueue = selectedToForwardMessageIDList
-    .map(messageID => TUIStore.getMessageModel(messageID))
-    .sort((a, b) => a.time - b.time);
-  const forwardPromises = selectedConversationList.map((conversation) => {
-    const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
-      conversation,
-      messageType: TUIChatEngine.TYPES.MSG_MERGER,
+  /**
+   * 这里传递的是 coversationID
+   * 但为了实现 Transfer 的复用 这里用 userID 代替 ConversationID
+   */
+  const selectedConversationList = selectedConvIDWrapperList.map((convIDWrapper) => {
+    const { userID: conversationID } = convIDWrapper;
+    return TUIStore.getConversationModel(conversationID);
+  });
+  const singleForwardMessageID: string = TUIStore.getData(StoreName.CUSTOM, 'singleForwardMessageID');
+  const message = TUIStore.getMessageModel(singleForwardMessageID);
+  const sendMessageOptions: any = {};
+  for (let i = 0; i < selectedConversationList.length; i++) {
+    const conversation = selectedConversationList[i];
+    const { conversationID } = conversation;
+    sendMessageOptions[conversationID] = {
+      offlinePushInfo: createOfflinePushInfo(conversation),
     };
-    return TUIChatService.sendForwardMessage(
-      [conversation],
-      unsentMessageQueue,
-      {
-        needMerge: isMergeForward,
-        offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
-        params: {
-          needReadReceipt: isEnabledMessageReadReceiptGlobal(),
-        },
+  }
+  TUIChatService.sendForwardMessage(
+    selectedConversationList,
+    [message],
+    {
+      params: {
+        needReadReceipt: isEnabledMessageReadReceiptGlobal(),
       },
-    );
-  });
-  Promise.allSettled(forwardPromises).then((results) => {
-    for (const result of results) {
-      const { status } = result;
-      if (status === 'rejected') {
-        const errorMessage = result.reason.code === 80001 ? TUITranslateService.t('TUIChat.内容包含敏感词汇') : result.reason.message as string;
-        Toast({
-          message: errorMessage,
-          type: TOAST_TYPE.ERROR,
-        });
-        break;
-      }
+      ...sendMessageOptions,
+    },
+  ).catch((error: { message: string; code: number }) => {
+    if (error.code === 80001) {
+      Toast({
+        message: TUITranslateService.t('内容包含敏感词汇'),
+        type: TOAST_TYPE.ERROR,
+      });
+    } else {
+      Toast({
+        message: error.message as string,
+        type: TOAST_TYPE.ERROR,
+      });
     }
   });
   closeForwardPanel();
-  emits('toggleMultipleSelectMode', false);
 }
 
-function getTransforRenderDataList() {
-  const conversationList = TUIStore.getData(StoreName.CONV, 'conversationList');
-  customConversationList.value = conversationList.map((conversation) => {
+function onSubmit(convIDWrapperList: Array<{ userID: string }>) {
+  if (convIDWrapperList?.length === 0) return;
+  finishSelected(convIDWrapperList);
+}
+
+function onConversationListUpdated(list: IConversationModel[]) {
+  customConversationList.value = list.map((conversation) => {
     return {
-      // To achieve reusability of Transfer, userID is used here instead of ConversationID
+      // 为了实现Transfer的复用,这里用userID代替ConversationID
       userID: conversation.conversationID,
       nick: conversation.getShowName(),
       avatar: conversation.getAvatar(),

+ 142 - 173
TUIKit/components/TUIChat/index.vue

@@ -14,82 +14,62 @@
             !isPC && 'tui-chat-H5-header',
             isUniFrameWork && 'tui-chat-uniapp-header',
           ]"
-          :headerExtensionList="headerExtensionList"
           @closeChat="closeChat"
         />
-        <Forward @toggleMultipleSelectMode="toggleMultipleSelectMode" />
-
+        <Forward />
         <MessageList
+          :address="address"
           ref="messageListRef"
           :class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']"
           :isGroup="isGroup"
           :groupID="groupID"
-          :isNotInGroup="isNotInGroup"
-          :isMultipleSelectMode="isMultipleSelectMode"
           @handleEditor="handleEditor"
           @closeInputToolBar="() => changeToolbarDisplayType('none')"
-          @toggleMultipleSelectMode="toggleMultipleSelectMode"
+          :jobInfoData="jobInfoData"
+          @emitStatus="emitStatus"
+        />
+        <!-- 兼容 uni 打包支付宝小程序 v-show 无效问题 -->
+        <MessageInputToolbar
+          v-if="isInputToolbarShow"
+          :class="[
+            'tui-chat-message-input-toolbar',
+            !isPC && 'tui-chat-h5-message-input-toolbar',
+            isUniFrameWork && 'tui-chat-uni-message-input-toolbar',
+          ]"
+          :displayType="inputToolbarDisplayType"
+          @insertEmoji="insertEmoji"
+          @changeToolbarDisplayType="changeToolbarDisplayType"
+          @scrollToLatestMessage="scrollToLatestMessage"
         />
-        <div
-          v-if="isNotInGroup"
-          :class="{
-            'tui-chat-leave-group': true,
-            'tui-chat-leave-group-mobile': isMobile,
-          }"
-        >
-          {{ leaveGroupReasonText }}
-        </div>
-        <MultipleSelectPanel
-          v-else-if="isMultipleSelectMode"
-          @oneByOneForwardMessage="oneByOneForwardMessage"
-          @mergeForwardMessage="mergeForwardMessage"
-          @toggleMultipleSelectMode="toggleMultipleSelectMode"
+        <MessageInput
+          ref="messageInputRef"
+          :class="[
+            'tui-chat-message-input',
+            !isPC && 'tui-chat-h5-message-input',
+            isUniFrameWork && 'tui-chat-uni-message-input',
+            isWeChat && 'tui-chat-wx-message-input',
+          ]"
+          :isMuted="false"
+          :muteText="TUITranslateService.t('TUIChat.您已被管理员禁言')"
+          :placeholder="TUITranslateService.t('TUIChat.请输入消息')"
+          :inputToolbarDisplayType="inputToolbarDisplayType"
+          @changeToolbarDisplayType="changeToolbarDisplayType"
+          :jobInfoData="jobInfoData"
         />
-        <template v-else>
-          <MessageInputToolbar
-            v-if="isInputToolbarShow"
-            :class="[
-              'tui-chat-message-input-toolbar',
-              !isPC && 'tui-chat-h5-message-input-toolbar',
-              isUniFrameWork && 'tui-chat-uni-message-input-toolbar',
-            ]"
-            :displayType="inputToolbarDisplayType"
-            @insertEmoji="insertEmoji"
-            @changeToolbarDisplayType="changeToolbarDisplayType"
-            @scrollToLatestMessage="scrollToLatestMessage"
-          />
-          <MessageInput
-            ref="messageInputRef"
-            :class="[
-              'tui-chat-message-input',
-              !isPC && 'tui-chat-h5-message-input',
-              isUniFrameWork && 'tui-chat-uni-message-input',
-              isWeChat && 'tui-chat-wx-message-input',
-            ]"
-            :enableAt="featureConfig.InputMention"
-            :isMuted="false"
-            :muteText="TUITranslateService.t('TUIChat.您已被管理员禁言')"
-            :placeholder="TUITranslateService.t('TUIChat.请输入消息')"
-            :inputToolbarDisplayType="inputToolbarDisplayType"
-            @changeToolbarDisplayType="changeToolbarDisplayType"
-          />
-        </template>
       </div>
-      <!-- Group Management -->
+      <!-- 群组管理 -->
       <div
-        v-if="
-          !isNotInGroup && isUniFrameWork && isGroup && headerExtensionList.length > 0
-        "
+        v-if="isUniFrameWork && isGroup && groupManageExt"
         class="group-profile"
         @click="handleGroup"
       >
-        {{ headerExtensionList[0].text }}
+        {{ groupManageExt.text }}
+        <!-- 更多 -->
       </div>
     </div>
   </div>
 </template>
 <script lang="ts" setup>
-import { ref, onMounted, onUnmounted, computed } from "../../adapter-vue";
 import TUIChatEngine, {
   TUITranslateService,
   TUIConversationService,
@@ -99,89 +79,140 @@ import TUIChatEngine, {
   IConversationModel,
 } from "@tencentcloud/chat-uikit-engine";
 import TUICore, { TUIConstants, ExtensionInfo } from "@tencentcloud/tui-core";
+import { ref, onMounted, onUnmounted, computed } from "../../adapter-vue";
 import ChatHeader from "./chat-header/index.vue";
 import MessageList from "./message-list/index.vue";
 import MessageInput from "./message-input/index.vue";
-import MultipleSelectPanel from "./mulitple-select-panel/index.vue";
 import Forward from "./forward/index.vue";
 import MessageInputToolbar from "./message-input-toolbar/index.vue";
-import { isPC, isWeChat, isUniFrameWork, isMobile } from "../../utils/env";
+import { isPC, isWeChat, isUniFrameWork } from "../../utils/env";
 import { ToolbarDisplayType } from "../../interface";
 import TUIChatConfig from "./config";
-// @Start uniapp use Chat only
-import { onLoad, onUnload } from "@dcloudio/uni-app";
-import { initChat, logout } from "./entry-chat-only.ts";
+import config from "@/request/config";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+const address = ref();
 
-onLoad((options) => {
-  initChat(options);
-});
-
-onUnload(() => {
-  // Whether logout is decided by yourself  when the page is unloaded. The default is false.
-  logout(false)
-    .then(() => {
-      // Handle success result from promise.then when you set true.
-    })
-    .catch(() => {
-      // handle error
-    });
+onShow(() => {
+  uni.$on("commitMapClick", function (res) {
+    address.value = res;
+    console.log("11111", address.value); //  为 B 页面传过来的值
+  });
 });
-// @End uniapp use Chat only
 
 const emits = defineEmits(["closeChat"]);
 
-const groupID = ref(undefined);
-const isGroup = ref(false);
-const isNotInGroup = ref(false);
-const notInGroupReason = ref<number>();
-const currentConversationID = ref();
-const isMultipleSelectMode = ref(false);
-const inputToolbarDisplayType = ref<ToolbarDisplayType>("none");
 const messageInputRef = ref();
 const messageListRef = ref<InstanceType<typeof MessageList>>();
-const headerExtensionList = ref<ExtensionInfo[]>([]);
-const featureConfig = TUIChatConfig.getFeatureConfig();
+const currentConversationID = ref();
+// 是否显示群组管理
+const isGroup = ref(false);
+const groupID = ref("");
+const groupManageExt = ref<ExtensionInfo | undefined>(undefined);
+const inputToolbarDisplayType = ref<ToolbarDisplayType>("none");
+// 沟通职位信息
+const jobInfoData = ref({});
+
+// 获取地址栏参数
+function getParamValue(paramName) {
+  const regExp = new RegExp("[?&]" + paramName + "=([^&#]*)");
+  const results = regExp.exec(window.location.href);
+  if (results) {
+    return decodeURIComponent(results[1]);
+  } else {
+    return null;
+  }
+}
 
 onMounted(() => {
+  initData();
+
   TUIStore.watch(StoreName.CONV, {
-    currentConversationID: onCurrentConversationIDUpdate,
     currentConversation: onCurrentConversationUpdate,
   });
 });
 
+const initData = () => {
+  let data = {
+    companyUserId: getParamValue("companyUserId"),
+    recruitUserId: getParamValue("recruitUserId"),
+    postId: getParamValue("postId"),
+  };
+      
+  // 发送消息后本地存储文本
+  let url = config.baseUrl + "/recruit/recruitCommunicate/status";
+  const header = {
+    "Content-Type": "application/x-www-form-urlencoded",
+    Authorization: uni.getStorageSync("token")
+      ? uni.getStorageSync("token")
+      : uni.getStorageSync("unitoken"),
+  };
+
+  uni.request({
+    url: url,
+    method: "GET",
+    header: header,
+    data: data,
+    success: function (res) {
+      jobInfoData.value = res.data.data;
+    },
+  });
+};
+
 onUnmounted(() => {
   TUIStore.unwatch(StoreName.CONV, {
-    currentConversationID: onCurrentConversationIDUpdate,
     currentConversation: onCurrentConversationUpdate,
   });
   reset();
 });
 
+function onCurrentConversationUpdate(conversation: IConversationModel) {
+  if (currentConversationID.value === conversation?.conversationID) {
+    return;
+  }
+  // TUIChat 每次切换会话,需要初始化 chatType;
+  TUIChatConfig.setChatType(conversation?.type);
+  // 由 TUICustomerServicePlugin 插件判断如果当前会话是客服会话,则设置 chatType 并激活会话。
+  TUICore.callService({
+    serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME,
+    method: TUIConstants.TUICustomerServicePlugin.SERVICE.METHOD.ACTIVE_CONVERSATION,
+    params: { conversationID: conversation?.conversationID },
+  });
+  currentConversationID.value = conversation?.conversationID;
+  isGroup.value = conversation?.type === TUIChatEngine.TYPES.CONV_GROUP;
+  // while chat converstaion changed, notify callkit current conversation type, c2c or group
+  TUICore.notifyEvent(
+    TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED,
+    TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED,
+    {
+      groupID: conversation?.groupProfile?.groupID,
+    }
+  );
+  if (
+    isUniFrameWork &&
+    isGroup.value &&
+    groupID.value !== conversation?.groupProfile?.groupID
+  ) {
+    const extList = TUICore.getExtensionList(
+      TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID,
+      {
+        filterManageGroup: !isGroup.value,
+      }
+    );
+    groupManageExt.value = extList[0];
+  }
+  if (isUniFrameWork && !isGroup.value) {
+    groupManageExt.value = undefined;
+  }
+  groupID.value = conversation?.groupProfile?.groupID;
+}
+
 const isInputToolbarShow = computed<boolean>(() => {
   return isUniFrameWork ? inputToolbarDisplayType.value !== "none" : true;
 });
 
-const leaveGroupReasonText = computed<string>(() => {
-  let text = "";
-  switch (notInGroupReason.value) {
-    case 4:
-      text = TUITranslateService.t("TUIChat.您已被管理员移出群聊");
-      break;
-    case 5:
-      text = TUITranslateService.t("TUIChat.该群聊已被解散");
-      break;
-    case 8:
-      text = TUITranslateService.t("TUIChat.您已退出该群聊");
-      break;
-    default:
-      text = TUITranslateService.t("TUIChat.您已退出该群聊");
-      break;
-  }
-  return text;
-});
-
+// 清空当前 conversationID
 const reset = () => {
-  TUIConversationService.switchConversation("");
+  TUIConversationService.switchConversation();
 };
 
 const closeChat = (conversationID: string) => {
@@ -211,9 +242,14 @@ const handleEditor = (message: IMessageModel, type: string) => {
       break;
   }
 };
+const emitStatus = (status: Boolean) => {
+  if (status) {
+    initData();
+  }
+};
 
 const handleGroup = () => {
-  headerExtensionList.value[0].listener.onClicked({ groupID: groupID.value });
+  groupManageExt.value.listener.onClicked({ groupID: groupID.value });
 };
 
 function changeToolbarDisplayType(type: ToolbarDisplayType) {
@@ -226,73 +262,6 @@ function changeToolbarDisplayType(type: ToolbarDisplayType) {
 function scrollToLatestMessage() {
   messageListRef.value?.scrollToLatestMessage();
 }
-
-function toggleMultipleSelectMode(visible?: boolean) {
-  isMultipleSelectMode.value =
-    visible === undefined ? !isMultipleSelectMode.value : visible;
-}
-
-function mergeForwardMessage() {
-  messageListRef.value?.mergeForwardMessage();
-}
-
-function oneByOneForwardMessage() {
-  messageListRef.value?.oneByOneForwardMessage();
-}
-
-function onCurrentConversationUpdate(conversation: IConversationModel) {
-  if (conversation?.operationType > 0) {
-    headerExtensionList.value = [];
-    isNotInGroup.value = true;
-    /**
-     * 4 - be removed from the group
-     * 5 - group is dismissed
-     * 8 - quit group
-     */
-    notInGroupReason.value = conversation?.operationType;
-  } else {
-    isNotInGroup.value = false;
-    notInGroupReason.value = undefined;
-  }
-}
-
-function onCurrentConversationIDUpdate(conversationID: string) {
-  if (currentConversationID.value === conversationID) {
-    return;
-  }
-
-  isGroup.value = false;
-  let conversationType = TUIChatEngine.TYPES.CONV_C2C;
-  if (conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP)) {
-    conversationType = TUIChatEngine.TYPES.CONV_GROUP;
-    isGroup.value = true;
-    groupID.value = conversationID.replace(TUIChatEngine.TYPES.CONV_GROUP, "");
-  }
-
-  headerExtensionList.value = [];
-  currentConversationID.value = conversationID;
-  isMultipleSelectMode.value = false;
-  // Initialize chatType
-  TUIChatConfig.setChatType(conversationType);
-  // While converstaion change success, notify callkit and roomkit、or other components.
-  TUICore.notifyEvent(
-    TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED,
-    TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED,
-    { groupID: groupID.value }
-  );
-  // The TUICustomerServicePlugin plugin determines if the current conversation is a customer service conversation, then sets chatType and activates the conversation.
-  TUICore.callService({
-    serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME,
-    method: TUIConstants.TUICustomerServicePlugin.SERVICE.METHOD.ACTIVE_CONVERSATION,
-    params: { conversationID: conversationID },
-  });
-  // Get chat header extensions
-  if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.GROUP) {
-    headerExtensionList.value = TUICore.getExtensionList(
-      TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID
-    );
-  }
-}
 </script>
 
 <style scoped lang="scss" src="./style/index.scss"></style>

+ 0 - 297
TUIKit/components/TUIChat/indexlink.vue

@@ -1,297 +0,0 @@
-<template>
-  <div class="chat">
-    <div :class="['tui-chat', !isPC && 'tui-chat-h5']">
-      <div
-        v-if="!currentConversationID"
-        :class="['tui-chat-default', !isPC && 'tui-chat-h5-default']"
-      >
-        <slot />
-      </div>
-      <div v-if="currentConversationID" :class="['tui-chat', !isPC && 'tui-chat-h5']">
-        <ChatHeader
-          :class="[
-            'tui-chat-header',
-            !isPC && 'tui-chat-H5-header',
-            isUniFrameWork && 'tui-chat-uniapp-header',
-          ]"
-          :headerExtensionList="headerExtensionList"
-          @closeChat="closeChat"
-        />
-        <Forward @toggleMultipleSelectMode="toggleMultipleSelectMode" />
-        <MessageList
-          ref="messageListRef"
-          :class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']"
-          :isGroup="isGroup"
-          :groupID="groupID"
-          :isNotInGroup="isNotInGroup"
-          :isMultipleSelectMode="isMultipleSelectMode"
-          @handleEditor="handleEditor"
-          @closeInputToolBar="() => changeToolbarDisplayType('none')"
-          @toggleMultipleSelectMode="toggleMultipleSelectMode"
-        />
-        <div
-          v-if="isNotInGroup"
-          :class="{
-            'tui-chat-leave-group': true,
-            'tui-chat-leave-group-mobile': isMobile,
-          }"
-        >
-          {{ leaveGroupReasonText }}
-        </div>
-        <MultipleSelectPanel
-          v-else-if="isMultipleSelectMode"
-          @oneByOneForwardMessage="oneByOneForwardMessage"
-          @mergeForwardMessage="mergeForwardMessage"
-          @toggleMultipleSelectMode="toggleMultipleSelectMode"
-        />
-        <template v-else>
-          <MessageInputToolbar
-            v-if="isInputToolbarShow"
-            :class="[
-              'tui-chat-message-input-toolbar',
-              !isPC && 'tui-chat-h5-message-input-toolbar',
-              isUniFrameWork && 'tui-chat-uni-message-input-toolbar',
-            ]"
-            :displayType="inputToolbarDisplayType"
-            @insertEmoji="insertEmoji"
-            @changeToolbarDisplayType="changeToolbarDisplayType"
-            @scrollToLatestMessage="scrollToLatestMessage"
-          />
-          <MessageInput
-            ref="messageInputRef"
-            :class="[
-              'tui-chat-message-input',
-              !isPC && 'tui-chat-h5-message-input',
-              isUniFrameWork && 'tui-chat-uni-message-input',
-              isWeChat && 'tui-chat-wx-message-input',
-            ]"
-            :enableAt="featureConfig.InputMention"
-            :isMuted="false"
-            :muteText="TUITranslateService.t('TUIChat.您已被管理员禁言')"
-            :placeholder="TUITranslateService.t('TUIChat.请输入消息')"
-            :inputToolbarDisplayType="inputToolbarDisplayType"
-            @changeToolbarDisplayType="changeToolbarDisplayType"
-          />
-        </template>
-      </div>
-      <!-- Group Management -->
-      <div
-        v-if="
-          !isNotInGroup && isUniFrameWork && isGroup && headerExtensionList.length > 0
-        "
-        class="group-profile"
-        @click="handleGroup"
-      >
-        {{ headerExtensionList[0].text }}
-      </div>
-    </div>
-  </div>
-</template>
-<script lang="ts" setup>
-import { ref, onMounted, onUnmounted, computed } from "../../adapter-vue";
-import TUIChatEngine, {
-  TUITranslateService,
-  TUIConversationService,
-  TUIStore,
-  StoreName,
-  IMessageModel,
-  IConversationModel,
-} from "@tencentcloud/chat-uikit-engine";
-import TUICore, { TUIConstants, ExtensionInfo } from "@tencentcloud/tui-core";
-import ChatHeader from "./chat-header/index.vue";
-import MessageList from "./message-list/index.vue";
-import MessageInput from "./message-input/index.vue";
-import MultipleSelectPanel from "./mulitple-select-panel/index.vue";
-import Forward from "./forward/index.vue";
-import MessageInputToolbar from "./message-input-toolbar/index.vue";
-import { isPC, isWeChat, isUniFrameWork, isMobile } from "../../utils/env";
-import { ToolbarDisplayType } from "../../interface";
-import TUIChatConfig from "./config";
-// @Start uniapp use Chat only
-import { onLoad, onUnload } from "@dcloudio/uni-app";
-import { initChat, logout } from "./entry-chat-only.ts";
-
-onLoad((options) => {
-  initChat(options);
-});
-
-onUnload(() => {
-  // Whether logout is decided by yourself  when the page is unloaded. The default is false.
-  logout(false)
-    .then(() => {
-      // Handle success result from promise.then when you set true.
-    })
-    .catch(() => {
-      // handle error
-    });
-});
-// @End uniapp use Chat only
-
-const emits = defineEmits(["closeChat"]);
-
-const groupID = ref(undefined);
-const isGroup = ref(false);
-const isNotInGroup = ref(false);
-const notInGroupReason = ref<number>();
-const currentConversationID = ref();
-const isMultipleSelectMode = ref(false);
-const inputToolbarDisplayType = ref<ToolbarDisplayType>("none");
-const messageInputRef = ref();
-const messageListRef = ref<InstanceType<typeof MessageList>>();
-const headerExtensionList = ref<ExtensionInfo[]>([]);
-const featureConfig = TUIChatConfig.getFeatureConfig();
-
-onMounted(() => {
-  TUIStore.watch(StoreName.CONV, {
-    currentConversationID: onCurrentConversationIDUpdate,
-    currentConversation: onCurrentConversationUpdate,
-  });
-});
-
-onUnmounted(() => {
-  TUIStore.unwatch(StoreName.CONV, {
-    currentConversationID: onCurrentConversationIDUpdate,
-    currentConversation: onCurrentConversationUpdate,
-  });
-  reset();
-});
-
-const isInputToolbarShow = computed<boolean>(() => {
-  return isUniFrameWork ? inputToolbarDisplayType.value !== "none" : true;
-});
-
-const leaveGroupReasonText = computed<string>(() => {
-  let text = "";
-  switch (notInGroupReason.value) {
-    case 4:
-      text = TUITranslateService.t("TUIChat.您已被管理员移出群聊");
-      break;
-    case 5:
-      text = TUITranslateService.t("TUIChat.该群聊已被解散");
-      break;
-    case 8:
-      text = TUITranslateService.t("TUIChat.您已退出该群聊");
-      break;
-    default:
-      text = TUITranslateService.t("TUIChat.您已退出该群聊");
-      break;
-  }
-  return text;
-});
-
-const reset = () => {
-  TUIConversationService.switchConversation("");
-};
-
-const closeChat = (conversationID: string) => {
-  emits("closeChat", conversationID);
-  reset();
-};
-
-const insertEmoji = (emojiObj: object) => {
-  messageInputRef.value?.insertEmoji(emojiObj);
-};
-
-const handleEditor = (message: IMessageModel, type: string) => {
-  if (!message || !type) return;
-  switch (type) {
-    case "reference":
-      // todo
-      break;
-    case "reply":
-      // todo
-      break;
-    case "reedit":
-      if (message?.payload?.text) {
-        messageInputRef?.value?.reEdit(message?.payload?.text);
-      }
-      break;
-    default:
-      break;
-  }
-};
-
-const handleGroup = () => {
-  headerExtensionList.value[0].listener.onClicked({ groupID: groupID.value });
-};
-
-function changeToolbarDisplayType(type: ToolbarDisplayType) {
-  inputToolbarDisplayType.value = inputToolbarDisplayType.value === type ? "none" : type;
-  if (inputToolbarDisplayType.value !== "none" && isUniFrameWork) {
-    uni.$emit("scroll-to-bottom");
-  }
-}
-
-function scrollToLatestMessage() {
-  messageListRef.value?.scrollToLatestMessage();
-}
-
-function toggleMultipleSelectMode(visible?: boolean) {
-  isMultipleSelectMode.value =
-    visible === undefined ? !isMultipleSelectMode.value : visible;
-}
-
-function mergeForwardMessage() {
-  messageListRef.value?.mergeForwardMessage();
-}
-
-function oneByOneForwardMessage() {
-  messageListRef.value?.oneByOneForwardMessage();
-}
-
-function onCurrentConversationUpdate(conversation: IConversationModel) {
-  if (conversation?.operationType > 0) {
-    headerExtensionList.value = [];
-    isNotInGroup.value = true;
-    /**
-     * 4 - be removed from the group
-     * 5 - group is dismissed
-     * 8 - quit group
-     */
-    notInGroupReason.value = conversation?.operationType;
-  } else {
-    isNotInGroup.value = false;
-    notInGroupReason.value = undefined;
-  }
-}
-
-function onCurrentConversationIDUpdate(conversationID: string) {
-  if (currentConversationID.value === conversationID) {
-    return;
-  }
-
-  isGroup.value = false;
-  let conversationType = TUIChatEngine.TYPES.CONV_C2C;
-  if (conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP)) {
-    conversationType = TUIChatEngine.TYPES.CONV_GROUP;
-    isGroup.value = true;
-    groupID.value = conversationID.replace(TUIChatEngine.TYPES.CONV_GROUP, "");
-  }
-
-  headerExtensionList.value = [];
-  currentConversationID.value = conversationID;
-  isMultipleSelectMode.value = false;
-  // Initialize chatType
-  TUIChatConfig.setChatType(conversationType);
-  // While converstaion change success, notify callkit and roomkit、or other components.
-  TUICore.notifyEvent(
-    TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED,
-    TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED,
-    { groupID: groupID.value }
-  );
-  // The TUICustomerServicePlugin plugin determines if the current conversation is a customer service conversation, then sets chatType and activates the conversation.
-  TUICore.callService({
-    serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME,
-    method: TUIConstants.TUICustomerServicePlugin.SERVICE.METHOD.ACTIVE_CONVERSATION,
-    params: { conversationID: conversationID },
-  });
-  // Get chat header extensions
-  if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.GROUP) {
-    headerExtensionList.value = TUICore.getExtensionList(
-      TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID
-    );
-  }
-}
-</script>
-
-<style scoped lang="scss" src="./style/index.scss"></style>

+ 14 - 26
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue

@@ -19,7 +19,7 @@
         <img
           v-if="currentTabItem.type === EMOJI_TYPE.BASIC"
           class="emoji"
-          :src="currentTabItem.url + BASIC_EMOJI_URL_MAPPING[childrenItem]"
+          :src="currentTabItem.url + basicEmojiMap[childrenItem]"
         >
         <img
           v-else-if="currentTabItem.type === EMOJI_TYPE.BIG"
@@ -28,7 +28,7 @@
         >
         <img
           v-else
-          class="emoji-custom emoji-big"
+          class="emoji-custom"
           :src="currentTabItem.url + childrenItem"
         >
       </li>
@@ -52,7 +52,7 @@
         >
         <img
           v-else
-          class="icon-custom icon-big"
+          class="icon-custom"
           :src="item.url + item.list[0]"
         >
       </li>
@@ -79,20 +79,18 @@ import Icon from '../../../common/Icon.vue';
 import faceIcon from '../../../../assets/icon/face.png';
 import { EMOJI_TYPE } from '.././../../../constant';
 import { isPC, isUniFrameWork } from '../../../../utils/env';
-import { IEmojiGroupList, IEmojiGroup } from '../../../../interface';
+import { IEmojiList, IEmojiListItem } from '../../../../interface';
 import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
-import { EMOJI_GROUP_LIST, BASIC_EMOJI_URL_MAPPING, convertKeyToEmojiName } from '../../emoji-config';
-import TUIChatConfig from '../../config';
+import { emojiList, basicEmojiMap, basicEmojiList } from '../../utils/emojiList';
 
 const emits = defineEmits(['insertEmoji', 'onClose', 'sendMessage']);
+const list = ref<IEmojiList>(emojiList);
 const currentTabIndex = ref<number>(0);
+const currentTabItem = ref<IEmojiListItem>(list?.value[0]);
+const currentEmojiList = ref<string[]>(list?.value[0]?.list);
 const currentConversation = ref();
 const emojiPickerDialog = ref();
 const emojiPickerListRef = ref();
-const featureConfig = TUIChatConfig.getFeatureConfig();
-const list = ref<IEmojiGroupList>(initEmojiList());
-const currentTabItem = ref<IEmojiGroup>(list?.value[0]);
-const currentEmojiList = ref<string[]>(list?.value[0]?.list);
 
 onMounted(() => {
   TUIStore.watch(StoreName.CONV, {
@@ -110,7 +108,8 @@ const toggleEmojiTab = (index: number) => {
   currentTabIndex.value = index;
   currentTabItem.value = list?.value[index];
   currentEmojiList.value = list?.value[index]?.list;
-  // web & h5 side scroll to top
+  // 滚动条回滚到顶部
+  // 原生 web & h5
   if (!isUniFrameWork) {
     emojiPickerListRef?.value && (emojiPickerListRef.value.scrollTop = 0);
   }
@@ -118,12 +117,12 @@ const toggleEmojiTab = (index: number) => {
 
 const select = (item: any, index: number) => {
   const options: any = {
-    emoji: { key: item, name: convertKeyToEmojiName(item) },
+    emoji: { key: item, name: basicEmojiList[item] },
     type: currentTabItem?.value?.type,
   };
   switch (currentTabItem?.value?.type) {
     case EMOJI_TYPE.BASIC:
-      options.url = currentTabItem?.value?.url + BASIC_EMOJI_URL_MAPPING[item];
+      options.url = currentTabItem?.value?.url + basicEmojiMap[item];
       if (isUniFrameWork) {
         uni.$emit('insert-emoji', options);
       } else {
@@ -142,14 +141,14 @@ const select = (item: any, index: number) => {
   isPC && emits('onClose');
 };
 
-const sendFaceMessage = (index: number, listItem: IEmojiGroup) => {
+const sendFaceMessage = (index: number, listItem: IEmojiListItem) => {
   const options = {
     to:
       currentConversation?.value?.groupProfile?.groupID
       || currentConversation?.value?.userProfile?.userID,
     conversationType: currentConversation?.value?.type,
     payload: {
-      index: listItem.emojiGroupID,
+      index: listItem.index,
       data: listItem.list[index],
     },
     needReadReceipt: isEnabledMessageReadReceiptGlobal(),
@@ -164,17 +163,6 @@ function sendMessage() {
 function onCurrentConversationUpdate(conversation: IConversationModel) {
   currentConversation.value = conversation;
 }
-
-function initEmojiList() {
-  return EMOJI_GROUP_LIST.filter((item) => {
-    if (item.type === EMOJI_TYPE.BASIC) {
-      return featureConfig.InputEmoji;
-    }
-    if (item.type === EMOJI_TYPE.BIG) {
-      return featureConfig.InputStickers;
-    }
-  });
-}
 </script>
 
 <style lang="scss" scoped src="./style/index.scss"></style>

+ 1 - 1
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/web.scss

@@ -1,5 +1,5 @@
 .emoji-picker {
-  width: 405px;
+  width: 450px;
   height: 300px;
   display: flex;
   flex-direction: column;

+ 112 - 107
TUIKit/components/TUIChat/message-input-toolbar/evaluate/index.vue

@@ -2,7 +2,7 @@
   <ToolbarItemContainer
     ref="container"
     :iconFile="evaluateIcon"
-    title="评价"
+    title="面试邀请"
     :needBottomPopup="true"
     :iconWidth="isUniFrameWork ? '26px' : '20px'"
     :iconHeight="isUniFrameWork ? '26px' : '20px'"
@@ -11,122 +11,104 @@
   >
     <div :class="['evaluate', !isPC && 'evaluate-h5']">
       <div :class="['evaluate-header', !isPC && 'evaluate-h5-header']">
-        <div
-          :class="[
-            'evaluate-header-content',
-            !isPC && 'evaluate-h5-header-content',
-          ]"
-        >
-          {{ TUITranslateService.t("Evaluate.请对本次服务进行评价") }}
+        <div :class="['evaluate-header-content', !isPC && 'evaluate-h5-header-content']">
+          {{ TUITranslateService.t("发送面试邀请") }}
         </div>
         <div
           v-if="!isPC"
-          :class="[
-            'evaluate-header-close',
-            !isPC && 'evaluate-h5-header-close',
-          ]"
+          :class="['evaluate-header-close', !isPC && 'evaluate-h5-header-close']"
           @click.stop="closeDialog"
         >
           {{ TUITranslateService.t("关闭") }}
         </div>
       </div>
       <div :class="['evaluate-content', !isPC && 'evaluate-h5-content']">
-        <ul
-          :class="[
-            'evaluate-content-list',
-            !isPC && 'evaluate-h5-content-list',
-          ]"
+        <picker
+          class="picker"
+          style="
+            height: 40px;
+            width: 94%;
+            background: #f8f8f8;
+            text-align: left;
+            line-height: 39px;
+            padding: 0px 11px;
+          "
+          mode="date"
+          :value="date"
+          @change="bindDateChange"
         >
-          <li
-            v-for="(item, index) in starList"
-            :key="index"
-            :class="[
-              'evaluate-content-list-item',
-              !isPC && 'evaluate-h5-content-list-item',
-            ]"
-            @click.stop="selectStar(index)"
-          >
-            <Icon
-              v-if="index <= currentStarIndex"
-              :file="starLightIcon"
-              :width="isPC ? '20px' : '30px'"
-              :height="isPC ? '20px' : '30px'"
-            />
-            <Icon
-              v-else
-              :file="starIcon"
-              :width="isPC ? '20px' : '30px'"
-              :height="isPC ? '20px' : '30px'"
-            />
-          </li>
-        </ul>
+          <view class="uni-input">{{ date ? date : "请选择面试时间" }}</view>
+        </picker>
+
         <textarea
           v-model="comment"
-          :class="[
-            'evaluate-content-text',
-            !isPC && 'evaluate-h5-content-text',
-          ]"
+          placeholder="请输入面试地址"
+          :class="['evaluate-content-text', !isPC && 'evaluate-h5-content-text']"
         />
-        <div
-          :class="[
-            'evaluate-content-button',
-            !isPC && 'evaluate-h5-content-button',
-          ]"
-        >
+        <input
+          style="
+            height: 40px;
+            width: 94%;
+            background: #f8f8f8;
+            text-align: left;
+            line-height: 39px;
+            padding: 0px 11px;
+            margin-bottom: 20px;
+          "
+          @input="replaceInput"
+          :value="inpvalue"
+          focus
+          placeholder="联系电话"
+        />
+
+        <div :class="['evaluate-content-button', !isPC && 'evaluate-h5-content-button']">
           <button
             :class="['btn', isEvaluateValid ? 'btn-valid' : 'btn-invalid']"
             @click="submitEvaluate"
           >
-            {{ TUITranslateService.t("Evaluate.提交评价") }}
+            {{ TUITranslateService.t("发送邀请") }}
           </button>
         </div>
       </div>
-      <div :class="['evaluate-adv', !isPC && 'evaluate-h5-adv']">
-        {{ TUITranslateService.t("Evaluate.服务评价工具") }}
-        {{ "(" + TUITranslateService.t("Evaluate.使用") }}
-        <a @click="openLink(Link.customMessage)">
-          {{ TUITranslateService.t(`Evaluate.${Link.customMessage.label}`) }}
-        </a>
-        {{ TUITranslateService.t("Evaluate.搭建") + ")" }}
-      </div>
     </div>
   </ToolbarItemContainer>
 </template>
 <script setup lang="ts">
-import TUIChatEngine, {
+import {
   TUITranslateService,
   TUIStore,
   StoreName,
   IConversationModel,
   TUIChatService,
   SendMessageParams,
-  SendMessageOptions,
-} from '@tencentcloud/chat-uikit-engine';
-import { ref, computed } from '../../../../adapter-vue';
-import ToolbarItemContainer from '../toolbar-item-container/index.vue';
-import evaluateIcon from '../../../../assets/icon/evaluate.svg';
-import Link from '../../../../utils/documentLink';
-import Icon from '../../../common/Icon.vue';
-import starIcon from '../../../../assets/icon/star.png';
-import starLightIcon from '../../../../assets/icon/star-light.png';
-import { CHAT_MSG_CUSTOM_TYPE } from '../../../../constant';
-import { isPC, isH5, isUniFrameWork } from '../../../../utils/env';
-import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
-import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
+} from "@tencentcloud/chat-uikit-engine";
+import { ref, computed } from "../../../../adapter-vue";
+import ToolbarItemContainer from "../toolbar-item-container/index.vue";
+import evaluateIcon from "../../../../assets/icon/evaluate.svg";
+import Link from "../../../../utils/documentLink";
+import Icon from "../../../common/Icon.vue";
+import starIcon from "../../../../assets/icon/star.png";
+import starLightIcon from "../../../../assets/icon/star-light.png";
+import { CHAT_MSG_CUSTOM_TYPE } from "../../../../constant";
+import { isPC, isH5, isUniFrameWork } from "../../../../utils/env";
+import { isEnabledMessageReadReceiptGlobal } from "../../utils/utils";
+import { createOfflinePushInfo } from "../../utils/sendMessage";
 
+const date = ref("");
+const inpvalue = ref("");
 const props = defineProps({
   starTotal: {
     type: Number,
     default: 5,
   },
 });
-const emits = defineEmits(['onDialogPopupShowOrHide']);
+const emits = defineEmits(["onDialogPopupShowOrHide"]);
 
 const container = ref();
 
 const starList = ref<number>(props.starTotal);
 const currentStarIndex = ref<number>(-1);
-const comment = ref('');
+const comment = ref("");
 const currentConversation = ref<IConversationModel>();
 
 TUIStore.watch(StoreName.CONV, {
@@ -135,15 +117,23 @@ TUIStore.watch(StoreName.CONV, {
   },
 });
 
-const isEvaluateValid = computed(() => comment.value.length || currentStarIndex.value >= 0);
+const isEvaluateValid = computed(
+  () => comment.value.length || currentStarIndex.value >= 0
+);
+const bindDateChange = (e) => {
+  date.value = e.detail.value;
+};
+const replaceInput = (e) => {
+  inpvalue.value = e.detail.value;
+};
 
 const onDialogShow = () => {
-  emits('onDialogPopupShowOrHide', true);
+  emits("onDialogPopupShowOrHide", true);
 };
 
 const onDialogClose = () => {
   resetEvaluate();
-  emits('onDialogPopupShowOrHide', false);
+  emits("onDialogPopupShowOrHide", false);
 };
 
 const openLink = () => {
@@ -158,7 +148,7 @@ const closeDialog = () => {
 
 const resetEvaluate = () => {
   currentStarIndex.value = -1;
-  comment.value = '';
+  comment.value = "";
 };
 
 const selectStar = (starIndex?: any) => {
@@ -170,37 +160,52 @@ const selectStar = (starIndex?: any) => {
 };
 
 const submitEvaluate = () => {
-  // The evaluate message must have at least one star or comment to be submitted.
+  // 评价消息,星星数和文本必须有一个才可以提交
   if (currentStarIndex.value < 0 && !comment.value.length) {
     return;
   }
-  const payload = {
-    data: JSON.stringify({
-      businessID: CHAT_MSG_CUSTOM_TYPE.EVALUATE,
-      version: 1,
-      score: currentStarIndex.value + 1,
-      comment: comment.value,
-    }),
-    description: '对本次的服务评价',
-    extension: '对本次的服务评价',
-  };
-  const options = {
-    to:
-      currentConversation?.value?.groupProfile?.groupID
-      || currentConversation?.value?.userProfile?.userID,
-    conversationType: currentConversation?.value?.type,
-    payload,
-    needReadReceipt: isEnabledMessageReadReceiptGlobal(),
-  };
-  const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
-    conversation: currentConversation.value,
-    payload: options.payload,
-    messageType: TUIChatEngine.TYPES.MSG_CUSTOM,
-  };
-  const sendMessageOptions: SendMessageOptions = {
-    offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
-  };
-  TUIChatService.sendCustomMessage(options as SendMessageParams, sendMessageOptions);
+
+  let promise = TUIChatService.sendCustomMessage({
+    payload: {
+      data: JSON.stringify({
+        // 订单类消息标识字段
+        businessID: "order",
+        // 订单标题
+        companyname: "云赋能网络信息有限公司",
+        interviewtime: date.value, //面试时间
+        Interviewlocation: comment.value, //面试地点
+        iphone: inpvalue.value, //联系方式
+        name: "王希望", //联系人
+        imageUrl:
+          "https://qcloudimg.tencent-cloud.cn/trisys/assets/product/images/SOOZNXCHkHcm50wX2ndp4.png",
+      }),
+      description: "",
+      extension: "",
+    },
+  });
+
+  // const options = {
+  //   to:
+  //     currentConversation?.value?.groupProfile?.groupID
+  //     || currentConversation?.value?.userProfile?.userID,
+  //   conversationType: currentConversation?.value?.type,
+  //   payload: {
+  //     data: JSON.stringify({
+  //       businessID: CHAT_MSG_CUSTOM_TYPE.EVALUATE,
+  //       version: 1,
+  //       score: currentStarIndex.value + 1,
+  //       comment: comment.value,
+  //     }),
+  //     description: '对本次的服务评价',
+  //     extension: '对本次的服务评价',
+  //   },
+  //   needReadReceipt: isEnabledMessageReadReceiptGlobal(),
+  // };
+  // const sendMessageOptions: any = {
+  //   offlinePushInfo: createOfflinePushInfo(currentConversation),
+  // };
+  // TUIChatService.sendCustomMessage(options as SendMessageParams, sendMessageOptions);
+  // 提交后关闭 dialog
   // close dialog after submit evaluate
   container?.value?.toggleDialogDisplay(false);
 };

+ 1 - 1
TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/h5.scss

@@ -4,7 +4,7 @@
   height: fit-content;
   border-radius: 0;
   background: #fff;
-  padding: 23px !important;
+  // padding: 23px !important;
   box-sizing: border-box;
 
   &-header {

+ 1 - 1
TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/web.scss

@@ -3,7 +3,7 @@
   z-index: 5;
   width: 315px;
   top: -255px;
-  padding: 12px;
+  // padding: 12px;
   display: flex;
   flex-direction: column;
   border-radius: 8px;

+ 3 - 12
TUIKit/components/TUIChat/message-input-toolbar/file-upload/index.vue

@@ -20,20 +20,18 @@
   </ToolbarItemContainer>
 </template>
 <script lang="ts" setup>
-import TUIChatEngine, {
+import {
   TUIChatService,
   TUIStore,
   StoreName,
   IConversationModel,
   SendMessageParams,
-  SendMessageOptions,
 } from '@tencentcloud/chat-uikit-engine';
 import { ref } from '../../../../adapter-vue';
 import ToolbarItemContainer from '../toolbar-item-container/index.vue';
 import fileIcon from '../../../../assets/icon/files.png';
 import { isPC, isUniFrameWork } from '../../../../utils/env';
 import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
-import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
 
 const inputRef = ref();
 const currentConversation = ref<IConversationModel>();
@@ -46,6 +44,7 @@ TUIStore.watch(StoreName.CONV, {
 
 const onIconClick = () => {
   if (isUniFrameWork) {
+    // uniapp app 不支持选择文件发送
     return;
   } else {
     inputRef?.value?.click && inputRef?.value?.click();
@@ -66,15 +65,7 @@ const sendFileMessage = (e: any) => {
     },
     needReadReceipt: isEnabledMessageReadReceiptGlobal(),
   } as SendMessageParams;
-  const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
-    conversation: currentConversation.value,
-    payload: options.payload,
-    messageType: TUIChatEngine.TYPES.MSG_FILE,
-  };
-  const sendMessageOptions: SendMessageOptions = {
-    offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
-  };
-  TUIChatService.sendFileMessage(options, sendMessageOptions);
+  TUIChatService.sendFileMessage(options);
   e.target.value = '';
 };
 </script>

+ 14 - 21
TUIKit/components/TUIChat/message-input-toolbar/image-upload/index.vue

@@ -23,13 +23,12 @@
   </ToolbarItemContainer>
 </template>
 <script lang="ts" setup>
-import TUIChatEngine, {
+import {
   TUIChatService,
   TUIStore,
   StoreName,
   IConversationModel,
   SendMessageParams,
-  SendMessageOptions,
 } from '@tencentcloud/chat-uikit-engine';
 import { TUIGlobal } from '@tencentcloud/universal-api';
 import { ref, computed } from '../../../../adapter-vue';
@@ -39,12 +38,11 @@ import imageIcon from '../../../../assets/icon/image.png';
 import imageUniIcon from '../../../../assets/icon/image-uni.png';
 import cameraUniIcon from '../../../../assets/icon/camera-uni.png';
 import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
-import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
 
 const props = defineProps({
-  // Image source: only valid for uni-app version, web version only supports selecting images from the album.
-  // album: Select from album
-  // camera: Take a photo using the camera
+  // 图片源, 仅uniapp版本有效, web版本仅支持从相册中选择图片
+  // album: 从相册中选择
+  // camera: 使用相机拍摄
   imageSourceType: {
     type: String,
     default: 'album',
@@ -85,23 +83,25 @@ const imageToolbarForShow = computed((): { icon: string; title: string } => {
 });
 
 const onIconClick = () => {
-  // uni-app send image
+  // uniapp 环境 发送图片
   if (isUniFrameWork) {
+    // 增加 TUIGlobal.chooseMedia 条件限制,防御 uni 打包其他平台小程序时由于打包问题导致 isWeChat 为 true 出现运行时报错
     if (isWeChat && TUIGlobal?.chooseMedia) {
+      // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替
       TUIGlobal?.chooseMedia({
         count: 1,
-        mediaType: ['image'],
-        sizeType: ['original', 'compressed'],
-        sourceType: [props.imageSourceType], // Use camera or select from album.
+        mediaType: ['image'], // 图片
+        sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
+        sourceType: [props.imageSourceType], // 从相册选择或使用相机拍摄
         success: function (res: any) {
           sendImageMessage(res);
         },
       });
     } else {
-      // uni-app H5/App send image
+      // uniapp h5/app 发送图片
       TUIGlobal?.chooseImage({
         count: 1,
-        sourceType: [props.imageSourceType], // Use camera or select from album.
+        sourceType: [props.imageSourceType], // 从相册选择或使用相机拍摄
         success: function (res) {
           sendImageMessage(res);
         },
@@ -136,15 +136,8 @@ const sendImageMessage = (files: any) => {
     },
     needReadReceipt: isEnabledMessageReadReceiptGlobal(),
   } as SendMessageParams;
-  const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
-    conversation: currentConversation.value,
-    payload: options.payload,
-    messageType: TUIChatEngine.TYPES.MSG_IMAGE,
-  };
-  const sendMessageOptions: SendMessageOptions = {
-    offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
-  };
-  TUIChatService.sendImageMessage(options, sendMessageOptions);
+  // todo: 需要处理uniapp文件没有宽高的变形问题,需要linda看看
+  TUIChatService.sendImageMessage(options);
 };
 </script>
 

+ 63 - 95
TUIKit/components/TUIChat/message-input-toolbar/index.vue

@@ -23,13 +23,14 @@
             'message-input-toolbar-uni-list',
           ]"
         >
-          <ImageUpload v-if="featureConfig.InputImage" imageSourceType="camera" />
-          <ImageUpload v-if="featureConfig.InputImage" imageSourceType="album" />
-          <VideoUpload v-if="featureConfig.InputVideo" videoSourceType="album" />
-          <VideoUpload v-if="featureConfig.InputVideo" videoSourceType="camera" />
-          <template v-if="currentExtensionList.length > 0">
+          <ImageUpload imageSourceType="camera" />
+          <ImageUpload imageSourceType="album" />
+          <!-- <OtgoingCAll></OtgoingCAll> -->
+          <!-- <VideoUpload videoSourceType="album" />
+          <VideoUpload videoSourceType="camera" /> -->
+          <!-- <template v-if="currentExtensionList[0]">
             <div
-              v-for="(extension, index) in currentExtensionList.slice(0, slicePos)"
+              v-for="(extension, index) in currentExtensionList.slice(0, 4)"
               :key="index"
             >
               <ToolbarItemContainer
@@ -42,30 +43,18 @@
                 @onIconClick="onExtensionClick(extension)"
               />
             </div>
-          </template>
-          <!-- <template v-if="neededCountFirstPage === 1">
-            <Evaluate
-              v-if="featureConfig.InputEvaluation"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
-            <Words
-              v-else-if="featureConfig.InputQuickReplies"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
-          </template>
-          <template v-if="neededCountFirstPage > 1">
-            <Evaluate
-              v-if="featureConfig.InputEvaluation"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
-            <Words
-              v-if="featureConfig.InputQuickReplies"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
           </template> -->
+          <!-- <Evaluate
+            v-if="currentExtensionList.length < 4"
+            @onDialogPopupShowOrHide="handleSwiperDotShow"
+          /> -->
+          <Words
+            v-if="currentExtensionList.length < 3"
+            @onDialogPopupShowOrHide="handleSwiperDotShow"
+          />
         </swiper-item>
         <swiper-item
-          v-if="neededCountFirstPage <= 1"
+          v-if="currentExtensionList[2] && currentExtensionList.length >= 3"
           :class="[
             'message-input-toolbar-list',
             'message-input-toolbar-h5-list',
@@ -73,7 +62,7 @@
           ]"
         >
           <div
-            v-for="(extension, index) in currentExtensionList.slice(slicePos)"
+            v-for="(extension, index) in currentExtensionList.slice(4)"
             :key="index"
           >
             <ToolbarItemContainer
@@ -86,22 +75,14 @@
               @onIconClick="onExtensionClick(extension)"
             />
           </div>
-          <template v-if="neededCountFirstPage === 1">
-            <Words
-              v-if="featureConfig.InputQuickReplies"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
-          </template>
-          <template v-else>
-            <Evaluate
-              v-if="featureConfig.InputEvaluation"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
-            <Words
-              v-if="featureConfig.InputQuickReplies"
-              @onDialogPopupShowOrHide="handleSwiperDotShow"
-            />
-          </template>
+          <Evaluate
+            v-if="currentExtensionList.length >= 4"
+            @onDialogPopupShowOrHide="handleSwiperDotShow"
+          />
+          <Words
+            v-if="currentExtensionList.length >= 3"
+            @onDialogPopupShowOrHide="handleSwiperDotShow"
+          />
         </swiper-item>
       </swiper>
     </div>
@@ -125,6 +106,8 @@ import TUIChatEngine, {
 import TUICore, { ExtensionInfo, TUIConstants } from "@tencentcloud/tui-core";
 import ImageUpload from "./image-upload/index.vue";
 import VideoUpload from "./video-upload/index.vue";
+import OtgoingCAll from "./otgoing-call/index.vue";
+
 import Evaluate from "./evaluate/index.vue";
 import Words from "./words/index.vue";
 import ToolbarItemContainer from "./toolbar-item-container/index.vue";
@@ -133,7 +116,6 @@ import UserSelector from "./user-selector/index.vue";
 import TUIChatConfig from "../config";
 import { enableSampleTaskStatus } from "../../../utils/enableSampleTaskStatus";
 import { ToolbarDisplayType } from "../../../interface";
-import OfflinePushInfoManager, { PUSH_SCENE } from "../offlinePushInfoManager/index";
 
 interface IProps {
   displayType: ToolbarDisplayType;
@@ -148,62 +130,54 @@ const userSelectorRef = ref();
 const currentUserSelectorExtension = ref<ExtensionInfo | null>();
 const currentExtensionList = ref<ExtensionInfo[]>([]);
 const isSwiperIndicatorDotsEnable = ref<boolean>(false);
-const featureConfig = TUIChatConfig.getFeatureConfig();
-const neededCountFirstPage = ref<number>(8);
-const slicePos = ref<number>(0);
-
-const computeToolbarPaging = () => {
-  if (featureConfig.InputImage && featureConfig.InputVideo) {
-    neededCountFirstPage.value -= 4;
-  } else if (featureConfig.InputImage || featureConfig.InputVideo) {
-    neededCountFirstPage.value -= 2;
-  }
 
-  slicePos.value = neededCountFirstPage.value;
-  neededCountFirstPage.value -= currentExtensionList.value.length;
-
-  if (neededCountFirstPage.value === 1) {
-    isSwiperIndicatorDotsEnable.value =
-      featureConfig.InputEvaluation && featureConfig.InputQuickReplies;
-  } else if (neededCountFirstPage.value < 1) {
-    isSwiperIndicatorDotsEnable.value =
-      featureConfig.InputEvaluation || featureConfig.InputQuickReplies;
-  }
-};
+// extensions
+const extensionList: ExtensionInfo[] = [
+  ...TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID),
+];
 
 const getExtensionList = (conversationID: string) => {
   if (!conversationID) {
-    return;
+    // uniapp build ios app has null in last index need to filter
+    return (currentExtensionList.value = extensionList.filter(
+      (extension) => extension
+    ));
   }
   const chatType = TUIChatConfig.getChatType();
-  const params: Record<string, boolean | string> = { chatType };
-  // Backward compatibility: When callkit does not have chatType judgment, use filterVoice and filterVideo to filter
-  if (chatType === TUIConstants.TUIChat.TYPE.CUSTOMER_SERVICE) {
-    params.filterVoice = true;
-    params.filterVideo = true;
+  const options: any = {
+    chatType,
+  };
+  // 向下兼容,callkit 没有chatType 判断时,使用 filterVoice、filterVideo 过滤
+  if (chatType === "customerService") {
+    options.filterVoice = true;
+    options.filterVideo = true;
     enableSampleTaskStatus("customerService");
   }
-  // uni-app build ios app has null in last index need to filter
+  // uniapp build ios app has null in last index need to filter
   currentExtensionList.value = [
-    ...TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID, params),
-  ].filter((extension: ExtensionInfo) => {
-    if (extension?.data?.name === "search") {
-      return featureConfig.MessageSearch;
-    }
-    return true;
-  });
+    ...TUICore.getExtensionList(
+      TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID,
+      options
+    ),
+  ].filter((extension) => extension);
 };
 
 const onCurrentConversationUpdate = (conversation: IConversationModel) => {
   if (
     conversation?.conversationID &&
-    conversation.conversationID !== currentConversation.value?.conversationID
+    currentConversation.value?.conversationID !== conversation?.conversationID
   ) {
-    getExtensionList(conversation.conversationID);
-    computeToolbarPaging();
+    getExtensionList(conversation?.conversationID);
+    if (currentExtensionList.value.length > 2) {
+      isSwiperIndicatorDotsEnable.value = true;
+    }
   }
   currentConversation.value = conversation;
-  isGroup.value = currentConversation?.value?.type === TUIChatEngine.TYPES.CONV_GROUP;
+  if (currentConversation?.value?.type === TUIChatEngine.TYPES.CONV_GROUP) {
+    isGroup.value = true;
+  } else {
+    isGroup.value = false;
+  }
 };
 
 onMounted(() => {
@@ -245,13 +219,11 @@ const onCallExtensionClicked = (extension: ExtensionInfo, callType: number) => {
     extension?.listener?.onClicked?.({
       userIDList: [currentConversation?.value?.conversationID?.slice(3)],
       type: callType,
-      callParams: {
-        offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(PUSH_SCENE.CALL),
-      },
     });
   } else if (isGroup.value) {
     currentUserSelectorExtension.value = extension;
-    userSelectorRef?.value?.toggleShow && userSelectorRef.value.toggleShow(true);
+    userSelectorRef?.value?.toggleShow &&
+      userSelectorRef.value.toggleShow(true);
   }
 };
 
@@ -263,12 +235,7 @@ const genExtensionText = (extension: any) => {
 };
 
 const onUserSelectorSubmit = (selectedInfo: any) => {
-  currentUserSelectorExtension.value?.listener?.onClicked?.({
-    ...selectedInfo,
-    callParams: {
-      offlinePushInfo: OfflinePushInfoManager.getOfflinePushInfo(PUSH_SCENE.CALL),
-    },
-  });
+  currentUserSelectorExtension.value?.listener?.onClicked?.(selectedInfo);
   currentUserSelectorExtension.value = null;
 };
 
@@ -277,7 +244,8 @@ const onUserSelectorCancel = () => {
 };
 
 const handleSwiperDotShow = (showStatus: boolean) => {
-  isSwiperIndicatorDotsEnable.value = neededCountFirstPage.value <= 1 && !showStatus;
+  isSwiperIndicatorDotsEnable.value =
+    currentExtensionList.value.length > 2 ? !showStatus : false;
 };
 </script>
 <script lang="ts">

+ 2 - 0
TUIKit/components/TUIChat/message-input-toolbar/otgoing-call/index.ts

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

+ 117 - 0
TUIKit/components/TUIChat/message-input-toolbar/otgoing-call/index.vue

@@ -0,0 +1,117 @@
+<template>
+  <ToolbarItemContainer
+    ref="container"
+    :iconFile="evaluateIcon"
+    title="打电话"
+    :needBottomPopup="true"
+    :iconWidth="isUniFrameWork ? '26px' : '20px'"
+    :iconHeight="isUniFrameWork ? '26px' : '20px'"
+    @onDialogShow="onDialogShow"
+    @onDialogClose="onDialogClose"
+  >
+    <div :class="['evaluate', !isPC && 'evaluate-h5']">
+      <div :class="['evaluate-content-button', !isPC && 'evaluate-h5-content-button']">
+        <button
+          :class="['btn', isEvaluateValid ? 'btn-valid' : 'btn-invalid']"
+          @click="submitEvaluate"
+        >
+          {{ TUITranslateService.t("发送手机号") }}
+        </button>
+      </div>
+    </div>
+  </ToolbarItemContainer>
+</template>
+<script setup lang="ts">
+import {
+  TUITranslateService,
+  TUIStore,
+  StoreName,
+  IConversationModel,
+  TUIChatService,
+  SendMessageParams,
+} from "@tencentcloud/chat-uikit-engine";
+import { ref, computed } from "../../../../adapter-vue";
+import ToolbarItemContainer from "../toolbar-item-container/index.vue";
+import evaluateIcon from "../../../../assets/icon/evaluate.svg";
+import Link from "../../../../utils/documentLink";
+import Icon from "../../../common/Icon.vue";
+import starIcon from "../../../../assets/icon/star.png";
+import starLightIcon from "../../../../assets/icon/star-light.png";
+import { CHAT_MSG_CUSTOM_TYPE } from "../../../../constant";
+import { isPC, isH5, isUniFrameWork } from "../../../../utils/env";
+import { isEnabledMessageReadReceiptGlobal } from "../../utils/utils";
+import { createOfflinePushInfo } from "../../utils/sendMessage";
+const props = defineProps({
+  starTotal: {
+    type: Number,
+    default: 5,
+  },
+});
+const emits = defineEmits(["onDialogPopupShowOrHide"]);
+
+const container = ref();
+
+const starList = ref<number>(props.starTotal);
+const currentStarIndex = ref<number>(-1);
+const comment = ref("");
+const currentConversation = ref<IConversationModel>();
+
+TUIStore.watch(StoreName.CONV, {
+  currentConversation: (conversation: IConversationModel) => {
+    currentConversation.value = conversation;
+  },
+});
+
+const isEvaluateValid = computed(
+  () => comment.value.length || currentStarIndex.value >= 0
+);
+
+const onDialogShow = () => {
+  emits("onDialogPopupShowOrHide", true);
+};
+
+const onDialogClose = () => {
+  resetEvaluate();
+  emits("onDialogPopupShowOrHide", false);
+};
+
+const openLink = () => {
+  if (isPC || isH5) {
+    window.open(Link?.customMessage?.url);
+  }
+};
+
+const closeDialog = () => {
+  container?.value?.toggleDialogDisplay(false);
+};
+
+const resetEvaluate = () => {
+  currentStarIndex.value = -1;
+  comment.value = "";
+};
+
+const selectStar = (starIndex?: any) => {
+  if (currentStarIndex.value === starIndex) {
+    currentStarIndex.value = currentStarIndex.value - 1;
+  } else {
+    currentStarIndex.value = starIndex;
+  }
+};
+
+const submitEvaluate = () => {
+  let promise = TUIChatService.sendCustomMessage({
+    payload: {
+      data: JSON.stringify({
+        businessID: "phone",
+        title: "用户名称的手机号",
+        iphone: "18790411793",
+     
+      }),
+      description: "",
+      extension: "",
+    },
+  });
+  container?.value?.toggleDialogDisplay(false);
+};
+</script>
+<style scoped lang="scss" src="../../style/index.scss"></style>

+ 0 - 6
TUIKit/components/TUIChat/message-input-toolbar/style/uni.scss

@@ -7,12 +7,6 @@
   flex-direction: row;
   justify-content: space-between;
   z-index: 100;
-  user-select: none;
-  -webkit-touch-callout: none;
-  -webkit-user-select: none;
-  -khtml-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
 
   &-list {
     display: flex;

+ 2 - 2
TUIKit/components/TUIChat/message-input-toolbar/toolbar-item-container/index.vue

@@ -77,8 +77,8 @@ const props = defineProps({
     type: String,
     default: '20px',
   },
-  // Whether to display the bottom popup dialog on mobile devices
-  // Invalid on PC
+  // 是否需要移动端底部弹窗对话框展示, 默认为false
+  // pc端无效
   needBottomPopup: {
     type: Boolean,
     default: false,

+ 5 - 5
TUIKit/components/TUIChat/message-input-toolbar/user-selector/index.vue

@@ -30,7 +30,7 @@ import Transfer from '../../../common/Transfer/index.vue';
 import { isPC } from '../../../../utils/env';
 
 const props = defineProps({
-  // type: voiceCall/groupCall/...
+  // 类型: voiceCall/groupCall/...
   type: {
     type: String,
     default: '',
@@ -47,8 +47,8 @@ const props = defineProps({
 const emits = defineEmits(['submit', 'cancel']);
 const show = ref<boolean>(false);
 const groupID = ref<string>('');
-const memberList = ref<any[]>([]);
-const searchMemberList = ref<any[]>([]);
+const memberList = ref<Array<any>>([]);
+const searchMemberList = ref<Array<any>>([]);
 const selfUserID = ref<string>('');
 const titleMap: any = {
   voiceCall: '发起群语音',
@@ -97,8 +97,8 @@ const search = (searchInfo: string) => {
   searchMemberList.value = results?.length ? results : memberList.value;
 };
 
-const submit = (selectedMemberList: string[]) => {
-  const userIDList: string[] = [];
+const submit = (selectedMemberList: Array<string>) => {
+  const userIDList: Array<string> = [];
   selectedMemberList?.forEach((user: any) => {
     user?.userID && userIDList.push(user.userID);
   });

+ 19 - 26
TUIKit/components/TUIChat/message-input-toolbar/video-upload/index.vue

@@ -4,11 +4,12 @@
     :title="handleTitle()"
     :needDialog="false"
     :iconWidth="isUniFrameWork ? '32px' : '21px'"
-    :iconHeight="isUniFrameWork
-      ? props.videoSourceType === 'album'
-        ? '20px'
-        : '25px'
-      : '18px'
+    :iconHeight="
+      isUniFrameWork
+        ? props.videoSourceType === 'album'
+          ? '20px'
+          : '25px'
+        : '18px'
     "
     @onIconClick="onIconClick"
   >
@@ -25,13 +26,12 @@
   </ToolbarItemContainer>
 </template>
 <script lang="ts" setup>
-import TUIChatEngine, {
+import {
   TUIChatService,
   TUIStore,
   StoreName,
   IConversationModel,
   SendMessageParams,
-  SendMessageOptions,
 } from '@tencentcloud/chat-uikit-engine';
 import { TUIGlobal } from '@tencentcloud/universal-api';
 import { ref } from '../../../../adapter-vue';
@@ -41,12 +41,11 @@ import videoIcon from '../../../../assets/icon/video.png';
 import videoUniIcon from '../../../../assets/icon/video-uni.png';
 import cameraUniIcon from '../../../../assets/icon/camera-uni.png';
 import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
-import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../../offlinePushInfoManager/index';
 
 const props = defineProps({
-  // Video source, only valid for uni-app version, web version only supports selecting videos from files
-  // album: Select from files
-  // camera: Take a video using the camera
+  // 视频源, 仅uniapp版本有效, web版本仅支持从文件中选择视频
+  // album: 从文件中选择
+  // camera: 使用相机拍摄
   videoSourceType: {
     type: String,
     default: 'album',
@@ -86,23 +85,25 @@ const handleTitle = (): string => {
 };
 
 const onIconClick = () => {
-  // uni-app send video
+  // uniapp环境发送视频
   if (isUniFrameWork) {
+    // 增加 TUIGlobal.chooseMedia 条件限制,防御 uni 打包其他平台小程序时由于打包问题导致 isWeChat 为 true 出现运行时报错
     if (isWeChat && TUIGlobal?.chooseMedia) {
+      // 微信小程序从基础库 2.21.0 开始, wx.chooseVideo 停止维护,请使用 uni.chooseMedia 代替
       TUIGlobal?.chooseMedia({
-        mediaType: ['video'],
+        mediaType: ['video'], // 视频
         count: 1,
-        sourceType: [props.videoSourceType],
-        maxDuration: 60,
+        sourceType: [props.videoSourceType], // album 从相册选视频,camera 使用相机拍摄
+        maxDuration: 60, // 设置最长时间60s
         success: function (res: any) {
           sendVideoMessage(res);
         },
       });
     } else {
+      // uniapp h5/app 发送图片
       TUIGlobal?.chooseVideo({
         count: 1,
-        sourceType: [props.videoSourceType],
-        compressed: false,
+        sourceType: [props.videoSourceType], // 从相册选择或使用相机拍摄
         success: function (res: any) {
           sendVideoMessage(res);
         },
@@ -135,15 +136,7 @@ const sendVideoMessage = (file: any) => {
     },
     needReadReceipt: isEnabledMessageReadReceiptGlobal(),
   } as SendMessageParams;
-  const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
-    conversation: currentConversation.value,
-    payload: options.payload,
-    messageType: TUIChatEngine.TYPES.MSG_VIDEO,
-  };
-  const sendMessageOptions: SendMessageOptions = {
-    offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
-  };
-  TUIChatService.sendVideoMessage(options, sendMessageOptions);
+  TUIChatService.sendVideoMessage(options);
 };
 </script>
 

+ 15 - 14
TUIKit/components/TUIChat/message-input-toolbar/words/index.vue

@@ -1,6 +1,5 @@
 <template>
   <ToolbarItemContainer
-    ref="container"
     :iconFile="wordsIcon"
     title="常用语"
     :needBottomPopup="true"
@@ -8,6 +7,7 @@
     :iconHeight="isUniFrameWork ? '26px' : '20px'"
     @onDialogShow="onDialogShow"
     @onDialogClose="onDialogClose"
+    ref="container"
   >
     <div :class="['words', !isPC && 'words-h5']">
       <div :class="['words-header', !isPC && 'words-h5-header']">
@@ -24,9 +24,9 @@
       </div>
       <ul :class="['words-list', !isPC && 'words-h5-list']">
         <li
+          :class="['words-list-item', !isPC && 'words-h5-list-item']"
           v-for="(item, index) in wordsList"
           :key="index"
-          :class="['words-list-item', !isPC && 'words-h5-list-item']"
           @click="selectWord(item)"
         >
           {{ TUITranslateService.t(`Words.${item.value}`) }}
@@ -43,15 +43,15 @@ import {
   IConversationModel,
   SendMessageParams,
   TUIChatService,
-} from '@tencentcloud/chat-uikit-engine';
-import { ref } from '../../../../adapter-vue';
-import ToolbarItemContainer from '../toolbar-item-container/index.vue';
-import wordsIcon from '../../../../assets/icon/words.svg';
-import { wordsList } from '../../utils/wordsList';
-import { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
-import { isPC, isUniFrameWork } from '../../../../utils/env';
+} from "@tencentcloud/chat-uikit-engine";
+import { ref } from "../../../../adapter-vue";
+import ToolbarItemContainer from "../toolbar-item-container/index.vue";
+import wordsIcon from "../../../../assets/icon/words.svg";
+import { wordsList } from "../../utils/wordsList";
+import { isEnabledMessageReadReceiptGlobal } from "../../utils/utils";
+import { isPC, isUniFrameWork } from "../../../../utils/env";
 
-const emits = defineEmits(['onDialogPopupShowOrHide']);
+const emits = defineEmits(["onDialogPopupShowOrHide"]);
 const currentConversation = ref<IConversationModel>();
 const container = ref();
 
@@ -64,8 +64,8 @@ TUIStore.watch(StoreName.CONV, {
 const selectWord = (item: any) => {
   const options = {
     to:
-      currentConversation?.value?.groupProfile?.groupID
-      || currentConversation?.value?.userProfile?.userID,
+      currentConversation?.value?.groupProfile?.groupID ||
+      currentConversation?.value?.userProfile?.userID,
     conversationType: currentConversation?.value?.type,
     payload: {
       text: TUITranslateService.t(`Words.${item.value}`),
@@ -73,6 +73,7 @@ const selectWord = (item: any) => {
     needReadReceipt: isEnabledMessageReadReceiptGlobal(),
   } as SendMessageParams;
   TUIChatService.sendTextMessage(options);
+  // 提交后关闭 dialog
   // close dialog after submit evaluate
   container?.value?.toggleDialogDisplay(false);
 };
@@ -82,11 +83,11 @@ const closeDialog = () => {
 };
 
 const onDialogShow = () => {
-  emits('onDialogPopupShowOrHide', true);
+  emits("onDialogPopupShowOrHide", true);
 };
 
 const onDialogClose = () => {
-  emits('onDialogPopupShowOrHide', false);
+  emits("onDialogPopupShowOrHide", false);
 };
 </script>
 <style scoped lang="scss" src="./style/index.scss"></style>

+ 97 - 72
TUIKit/components/TUIChat/message-input/index.vue

@@ -1,9 +1,11 @@
 <template>
   <div :class="['message-input', !isPC && 'message-input-h5']">
-    <div class="audio-main-content-line">
+    <div class="flex-row">
       <MessageInputAudio
-        v-if="(isWeChat || isApp) && isRenderVoice"
+        v-if="isWeChat || isApp"
         :class="{
+          'message-input-audio': true,
+          'message-input-wx-audio': isWeChat,
           'message-input-wx-audio-open': displayType === 'audio',
         }"
         :isEnableAudio="displayType === 'audio'"
@@ -19,6 +21,7 @@
         :enableInput="props.enableInput"
         :enableAt="props.enableAt"
         :enableTyping="props.enableTyping"
+        :jobInfoData="props.jobInfoData"
         :isGroup="isGroup"
         @onTyping="onTyping"
         @onAt="onAt"
@@ -30,28 +33,21 @@
         @insertAt="insertAt"
         @onAtListOpen="onAtListOpen"
       />
-      <Icon
-        v-if="isRenderEmojiPicker"
-        class="icon icon-face"
-        :file="faceIcon"
-        :size="'23px'"
-        :hotAreaSize="'3px'"
-        @onClick="changeToolbarDisplayType('emojiPicker')"
-      />
-      <Icon
-        v-if="isRenderMore"
-        class="icon icon-more"
-        :file="moreIcon"
-        :size="'23px'"
-        :hotAreaSize="'3px'"
-        @onClick="changeToolbarDisplayType('tools')"
-      />
+      <div
+        class="message-input-emoji"
+        @click="changeToolbarDisplayType('emojiPicker')"
+      >
+        <Icon :file="faceIcon" class="icon icon-face" />
+      </div>
+      <div
+        class="message-input-more"
+        @click="changeToolbarDisplayType('tools')"
+      >
+        <Icon :file="moreIcon" class="icon icon-more" />
+      </div>
     </div>
     <div>
-      <MessageQuote
-        :style="{minWidth: 0}"
-        :displayType="displayType"
-      />
+      <MessageQuote :style="{ minWidth: 0 }" :displayType="displayType" />
     </div>
   </div>
 </template>
@@ -61,19 +57,18 @@ import TUIChatEngine, {
   StoreName,
   IMessageModel,
   IConversationModel,
-} from '@tencentcloud/chat-uikit-engine';
-import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
-import MessageInputEditor from './message-input-editor.vue';
-import MessageInputAt from './message-input-at/index.vue';
-import MessageInputAudio from './message-input-audio.vue';
-import MessageQuote from './message-input-quote/index.vue';
-import Icon from '../../common/Icon.vue';
-import faceIcon from '../../../assets/icon/face-uni.png';
-import moreIcon from '../../../assets/icon/more-uni.png';
-import { isPC, isH5, isWeChat, isApp } from '../../../utils/env';
-import { sendTyping } from '../utils/sendMessage';
-import { ToolbarDisplayType, InputDisplayType } from '../../../interface';
-import TUIChatConfig from '../config';
+} from "@tencentcloud/chat-uikit-engine";
+import { ref, watch, onMounted, onUnmounted } from "../../../adapter-vue";
+import MessageInputEditor from "./message-input-editor.vue";
+import MessageInputAt from "./message-input-at/index.vue";
+import MessageInputAudio from "./message-input-audio.vue";
+import MessageQuote from "./message-input-quote/index.vue";
+import Icon from "../../common/Icon.vue";
+import faceIcon from "../../../assets/icon/face-uni.png";
+import moreIcon from "../../../assets/icon/more-uni.png";
+import { isPC, isH5, isWeChat, isApp } from "../../../utils/env";
+import { sendTyping } from "../utils/sendMessage";
+import { ToolbarDisplayType, InputDisplayType } from "../../../interface";
 
 interface IProps {
   placeholder: string;
@@ -84,32 +79,29 @@ interface IProps {
   enableTyping?: boolean;
   replyOrReference?: Record<string, any>;
   inputToolbarDisplayType: ToolbarDisplayType;
+  jobInfoData?: Object;
 }
 interface IEmits {
-  (e: 'changeToolbarDisplayType', displayType: ToolbarDisplayType): void;
+  (e: "changeToolbarDisplayType", displayType: ToolbarDisplayType): void;
 }
 
 const emits = defineEmits<IEmits>();
 const props = withDefaults(defineProps<IProps>(), {
-  placeholder: 'this is placeholder',
+  placeholder: "this is placeholder",
   replyOrReference: () => ({}),
   isMuted: true,
-  muteText: '',
+  muteText: "",
   enableInput: true,
   enableAt: true,
   enableTyping: true,
-  inputToolbarDisplayType: 'none',
+  inputToolbarDisplayType: "none",
 });
 
 const editor = ref();
 const messageInputAtRef = ref();
 const currentConversation = ref<IConversationModel>();
 const isGroup = ref<boolean>(false);
-const displayType = ref<InputDisplayType>('editor');
-const featureConfig = TUIChatConfig.getFeatureConfig();
-const isRenderVoice = ref<boolean>(featureConfig.InputVoice);
-const isRenderEmojiPicker = ref<boolean>(featureConfig.InputEmoji || featureConfig.InputStickers);
-const isRenderMore = ref<boolean>(featureConfig.InputImage || featureConfig.InputVideo || featureConfig.InputEvaluation || featureConfig.InputQuickReplies);
+const displayType = ref<InputDisplayType>("editor");
 
 onMounted(() => {
   TUIStore.watch(StoreName.CONV, {
@@ -131,21 +123,24 @@ onUnmounted(() => {
   });
 });
 
-watch(() => props.inputToolbarDisplayType, (newVal: ToolbarDisplayType) => {
-  if (newVal !== 'none') {
-    changeDisplayType('editor');
+watch(
+  () => props.inputToolbarDisplayType,
+  (newVal: ToolbarDisplayType) => {
+    if (newVal !== "none") {
+      changeDisplayType("editor");
+    }
   }
-});
+);
 
 function changeDisplayType(display: InputDisplayType) {
   displayType.value = display;
-  if (display === 'audio') {
-    emits('changeToolbarDisplayType', 'none');
+  if (display === "audio") {
+    emits("changeToolbarDisplayType", "none");
   }
 }
 
 function changeToolbarDisplayType(displayType: ToolbarDisplayType) {
-  emits('changeToolbarDisplayType', displayType);
+  emits("changeToolbarDisplayType", displayType);
 }
 
 const onTyping = (inputContentEmpty: boolean, inputBlur: boolean) => {
@@ -158,7 +153,7 @@ const onAt = (show: boolean) => {
 
 const onFocus = () => {
   if (isH5) {
-    emits('changeToolbarDisplayType', 'none');
+    emits("changeToolbarDisplayType", "none");
   }
 };
 
@@ -174,6 +169,7 @@ const onAtListOpen = () => {
   editor?.value?.blur && editor?.value?.blur();
 };
 
+// 消息撤回后重新编辑
 const reEdit = (content: any) => {
   editor?.value?.resetEditor();
   editor?.value?.setEditorContent(content);
@@ -181,13 +177,18 @@ const reEdit = (content: any) => {
 
 function onCurrentConversationUpdated(conversation: IConversationModel) {
   currentConversation.value = conversation;
-  isGroup.value = currentConversation.value?.type === TUIChatEngine.TYPES.CONV_GROUP;
+  isGroup.value =
+    currentConversation.value?.type === TUIChatEngine.TYPES.CONV_GROUP;
 }
 
-function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
+function onQuoteMessageUpdated(options?: {
+  message: IMessageModel;
+  type: string;
+}) {
+  // 当有引用消息时切换为文字输入模式
   // switch text input mode when there is a quote message
-  if (options?.message && options?.type === 'quote') {
-    changeDisplayType('editor');
+  if (options?.message && options?.type === "quote") {
+    changeDisplayType("editor");
   }
 }
 
@@ -200,42 +201,66 @@ defineExpose({
 <style scoped lang="scss">
 @import "../../../assets/styles/common";
 
-:not(not) {
-  display: flex;
-  flex-direction: column;
-  min-width: 0;
-  box-sizing: border-box;
-}
-
 .message-input {
   position: relative;
   display: flex;
   flex-direction: column;
   border: none;
+  height: 100%;
+  width: 100%;
+  max-height: 100%;
+  max-width: 100%;
   overflow: hidden;
   background: #ebf0f6;
 
-  &-h5 {
-    padding: 10px 10px 15px;
-  }
-
   &-editor {
     flex: 1;
     display: flex;
   }
 
+  &-button {
+    width: fit-content;
+  }
+
   .icon {
-    margin-left: 3px;
+    width: 23px;
+    height: 23px;
+
+    &-face {
+      margin: 7px;
+    }
+
+    &-more {
+      margin: 7px 0;
+    }
   }
 
-  &-wx-audio-open {
-    flex: 1;
+  &-wx {
+    &-audio {
+      &-open {
+        flex: 1;
+      }
+    }
+  }
+
+  &-emoji-picker {
+    padding-top: 10px;
   }
 }
 
-.audio-main-content-line {
+.message-input-h5 {
+  display: flex;
+  flex-direction: column;
+  height: calc(100% - 20px);
+  width: calc(100% - 20px);
+  max-height: 100%;
+  max-width: calc(100% - 20px);
+  padding: 10px;
+  overflow: hidden;
+}
+
+.flex-row {
   display: flex;
   flex-direction: row;
-  align-items: center;
 }
 </style>

+ 44 - 30
TUIKit/components/TUIChat/message-input/message-input-at/index.vue

@@ -1,12 +1,23 @@
 <template>
-  <BottomPopup :show="showAtList" @onClose="closeAt">
+  <BottomPopup
+    :show="showAtList"
+    @onClose="closeAt"
+  >
     <div
       ref="MessageInputAt"
       :class="[isPC ? 'message-input-at' : 'message-input-at-h5']"
     >
-      <div ref="dialog" class="member-list">
-        <header v-if="!isPC" class="member-list-title">
-          <span class="title">{{ TUITranslateService.t("TUIChat.选择提醒的人") }}</span>
+      <div
+        ref="dialog"
+        class="member-list"
+      >
+        <header
+          v-if="!isPC"
+          class="member-list-title"
+        >
+          <span class="title">{{
+            TUITranslateService.t("TUIChat.选择提醒的人")
+          }}</span>
         </header>
         <ul class="member-list-box">
           <li
@@ -17,7 +28,10 @@
             :class="[index === selectedIndex && 'selected']"
             @click="selectItem(index)"
           >
-            <img class="member-list-box-body-avatar" :src="handleMemberAvatar(item)" />
+            <img
+              class="member-list-box-body-avatar"
+              :src="handleMemberAvatar(item)"
+            >
             <span class="member-list-box-body-name">
               {{ handleMemberName(item) }}
             </span>
@@ -33,13 +47,13 @@ import TUIChatEngine, {
   StoreName,
   TUIGroupService,
   TUITranslateService,
-} from "@tencentcloud/chat-uikit-engine";
-import { TUIGlobal } from "@tencentcloud/universal-api";
-import { ref, watch } from "../../../../adapter-vue";
-import { isPC, isH5 } from "../../../../utils/env";
-import BottomPopup from "../../../common/BottomPopup/index.vue";
+} from '@tencentcloud/chat-uikit-engine';
+import { TUIGlobal } from '@tencentcloud/universal-api';
+import { ref, watch } from '../../../../adapter-vue';
+import { isPC, isH5 } from '../../../../utils/env';
+import BottomPopup from '../../../common/BottomPopup/index.vue';
 
-const emits = defineEmits(["onAtListOpen", "insertAt"]);
+const emits = defineEmits(['onAtListOpen', 'insertAt']);
 
 const MessageInputAt = ref();
 const memberListItems = ref();
@@ -54,13 +68,13 @@ const position = ref({
   top: 0,
 });
 const selectedIndex = ref(0);
-const currentConversationID = ref("");
+const currentConversationID = ref('');
 
 const all = {
   userID: TUIChatEngine.TYPES.MSG_AT_ALL,
-  nick: "所有人",
+  nick: '所有人',
   isAll: true,
-  avatar: "https://web.sdk.qcloud.com/im/assets/images/at.svg",
+  avatar: 'https://web.sdk.qcloud.com/im/assets/images/at.svg',
 };
 
 TUIStore.watch(StoreName.CONV, {
@@ -71,13 +85,13 @@ TUIStore.watch(StoreName.CONV, {
       allMemberList.value = [];
       showMemberList.value = [];
       isGroup.value = false;
-      TUIStore.update(StoreName.CUSTOM, "memberList", memberList.value);
-      if (currentConversationID?.value?.startsWith("GROUP")) {
+      TUIStore.update(StoreName.CUSTOM, 'memberList', memberList.value);
+      if (currentConversationID?.value?.startsWith('GROUP')) {
         isGroup.value = true;
         const groupID = currentConversationID?.value?.substring(5);
         TUIGroupService.switchGroup(groupID);
       } else {
-        TUIGroupService.switchGroup("");
+        TUIGroupService.switchGroup('');
       }
     }
   },
@@ -88,7 +102,7 @@ TUIStore.watch(StoreName.GRP, {
     memberList.value = list;
     allMemberList.value = [all, ...memberList.value];
     showMemberList.value = allMemberList.value;
-    TUIStore.update(StoreName.CUSTOM, "memberList", memberList.value);
+    TUIStore.update(StoreName.CUSTOM, 'memberList', memberList.value);
   },
 });
 
@@ -98,7 +112,7 @@ const toggleAtList = (show: boolean) => {
   }
   showAtList.value = show;
   if (showAtList.value) {
-    emits("onAtListOpen");
+    emits('onAtListOpen');
   }
 };
 const handleAtListPosition = (positionData: { top: number; left: number }) => {
@@ -127,10 +141,10 @@ watch(
     if (isH5 || !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";
-  }
+    MessageInputAt.value.style.left = position.value.left + 'px';
+    MessageInputAt.value.style.top
+      = position.value.top - MessageInputAt.value.clientHeight + 'px';
+  },
 );
 
 const closeAt = () => {
@@ -148,7 +162,7 @@ const selectItem = (index: number) => {
   } else {
     if (showMemberList?.value?.length) {
       const item = showMemberList?.value[index];
-      emits("insertAt", {
+      emits('insertAt', {
         id: (item as any)?.userID,
         label: (item as any)?.nick || (item as any)?.userID,
       });
@@ -159,8 +173,8 @@ const selectItem = (index: number) => {
 
 const handleMemberAvatar = (item: any) => {
   return (
-    (item as any)?.avatar ||
-    "https://bucket.sxdirectpurchase.com/wx/static/img/ImAvatar.png"
+    (item as any)?.avatar
+    || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
   );
 };
 
@@ -177,7 +191,7 @@ const handleMemberName = (item: any) => {
   max-height: 10rem;
   overflow: hidden auto;
   background: #fff;
-  box-shadow: 0 0.06rem 0.63rem 0 rgba(2, 16, 43, 0.15);
+  box-shadow: 0 0.06rem 0.63rem 0 rgba(2,16,43,0.15);
   border-radius: 0.13rem;
 }
 
@@ -188,7 +202,7 @@ const handleMemberName = (item: any) => {
     cursor: pointer;
 
     &:hover {
-      background: rgba(0, 110, 255, 0.1);
+      background: rgba(0,110,255,0.1);
     }
   }
 
@@ -209,7 +223,7 @@ const handleMemberName = (item: any) => {
 
     .selected,
     &:hover {
-      background: rgba(0, 110, 255, 0.1);
+      background: rgba(0,110,255,0.1);
     }
 
     &-name {
@@ -228,7 +242,7 @@ const handleMemberName = (item: any) => {
   }
 
   .selected {
-    background: rgba(0, 110, 255, 0.1);
+    background: rgba(0,110,255,0.1);
   }
 }
 

+ 25 - 26
TUIKit/components/TUIChat/message-input/message-input-audio.vue

@@ -8,13 +8,11 @@
     <Icon
       class="audio-message-icon"
       :file="audioIcon"
-      :size="'23px'"
-      :hotAreaSize="'3px'"
       @onClick="switchAudio"
     />
     <view
       v-if="props.isEnableAudio"
-      class="audio-input-touch-bar"
+      class="tui-message-input-main"
       @touchstart="handleTouchStart"
       @longpress="handleLongPress"
       @touchmove="handleTouchMove"
@@ -46,7 +44,7 @@ import {
   TUITranslateService,
 } from '@tencentcloud/chat-uikit-engine';
 import { TUIGlobal } from '@tencentcloud/universal-api';
-import { throttle } from 'lodash';
+import throttle from 'lodash/throttle';
 import Icon from '../../common/Icon.vue';
 import audioIcon from '../../../assets/icon/audio.svg';
 import { Toast, TOAST_TYPE } from '../../common/Toast/index';
@@ -90,16 +88,15 @@ const isAudioTouchBarShow = ref<boolean>(false);
 const currentConversation = ref<IConversationModel>();
 
 const recordConfig = {
-  // Duration of the recording, in ms, with a maximum value of 600000 (10 minutes)
+  // 录音的时长,单位 ms,最大值 600000(10 分钟)
   duration: 60000,
-  // Sampling rate
+  // 采样率
   sampleRate: 44100,
-  // Number of recording channels
+  // 录音通道数
   numberOfChannels: 1,
-  // Encoding bit rate
+  // 编码码率
   encodeBitRate: 192000,
-  // Audio format
-  // Select this format to create audio messages that can be interoperable across all chat platforms (Android, iOS, WeChat Mini Programs, and Web).
+  // 音频格式,选择此格式创建的音频消息,可以在即时通信 IM 全平台(Android、iOS、微信小程序和Web)互通
   format: 'mp3',
 };
 
@@ -108,7 +105,7 @@ function switchAudio() {
 }
 
 onMounted(() => {
-  // Register events for the audio recording manager
+  // 为声音录制管理器注册事件
   recorderManager.onStart(onRecorderStart);
   recorderManager.onStop(onRecorderStop);
   recorderManager.onError(onRecorderError);
@@ -152,7 +149,7 @@ function initRecorderData(options?: { hasError: boolean }) {
 
 function handleTouchStart() {
   if (isFingerTouchingScreen) {
-    // Compatibility: Ignore the recording generated by the user's first authorization on the APP.
+    // 兼容 APP 首次由于用户授权产生的录音需要忽略
     isFirstAuthrizedRecord = true;
   }
 }
@@ -169,6 +166,7 @@ const handleTouchMove = throttle(function (e) {
       firstTouchPageY = pageY;
     }
     const offset = (firstTouchPageY as number) - pageY;
+    // 录音时的手势上划移动距离对应文案变化
     if (offset > 150) {
       touchBarText.value = '抬起取消';
       modalText.value = '松开手指 取消发送';
@@ -185,6 +183,7 @@ const handleTouchMove = throttle(function (e) {
   }
 }, 100);
 
+// 手指离开页面滑动
 function handleTouchEnd() {
   isFingerTouchingScreen = false;
   recorderManager.stop();
@@ -192,8 +191,7 @@ function handleTouchEnd() {
 
 function onRecorderStart() {
   if (!isFingerTouchingScreen) {
-    // If recording starts but the finger leaves the screen,
-    // it means that the initial authorization popup interrupted the recording and it should be ignored.
+    // 如果开始录音但手指离开屏幕 说明是首次授权弹窗打断了录音 需要忽略
     isFirstAuthrizedRecord = true;
     recorderManager.stop();
     return;
@@ -207,7 +205,7 @@ function onRecorderStart() {
 
 function onRecorderStop(res: RecordResult) {
   if (isFirstAuthrizedRecord) {
-    // Compatibility: Ignore the recording generated by the user's first authorization on WeChat. This is not applicable to the APP.
+    // 兼容微信首次由于用户授权产生的录音需要忽略 对 APP 无效
     isFirstAuthrizedRecord = false;
     initRecorder();
     return;
@@ -218,12 +216,12 @@ function onRecorderStop(res: RecordResult) {
   }
   clearInterval(recordTimer);
   /**
-   * Compatible with uniapp for building apps
-   * Compatible with uniapp voice messages without duration
-   * Duration and fileSize need to be supplemented by the user
-   * File size = (Audio bitrate) * Length of time (in seconds) / 8
-   * res.tempFilePath stores the temporary path of the recorded audio file
-  */
+   * 兼容 uniapp 打包 app
+   * 兼容 uniapp 语音消息没有 duration
+   * duration 和 fileSize 需要用户自己补充
+   * 文件大小 = (音频码率) * 时间长度(单位:秒) / 8
+   * res.tempFilePath 存储录音文件的临时路径
+   */
   const tempFilePath = res.tempFilePath;
   const duration = res.duration ? res.duration : recordTime * 1000;
   const fileSize = res.fileSize ? res.fileSize : ((48 * recordTime) / 8) * 1024;
@@ -260,16 +258,17 @@ function onRecorderError() {
 .message-input-audio {
   display: flex;
   flex-direction: row;
-  align-items: center;
 
   .audio-message-icon {
-    margin-right: 3px;
+    width: 23px;
+    height: 23px;
+    justify-items: center;
+    padding: 7px 7px 7px 0;
   }
 
-  .audio-input-touch-bar {
-    height: 39px;
+  .tui-message-input-main {
     flex: 1;
-    border-radius: 10px;
+    border-radius: 9.4px;
     display: flex;
     flex-direction: row;
     justify-content: center;

+ 1 - 10
TUIKit/components/TUIChat/message-input/message-input-button.vue

@@ -7,10 +7,7 @@
       :disabled="false"
       @click="sendMessage"
     >
-      <p
-        v-if="displayHover"
-        class="message-input-button-hover"
-      >
+      <p class="message-input-button-hover">
         {{ TUITranslateService.t("TUIChat.按Enter发送,Ctrl+Enter换行") }}
       </p>
       {{ TUITranslateService.t("发送") }}
@@ -18,12 +15,8 @@
   </div>
 </template>
 <script setup lang="ts">
-import { ref } from '../../../adapter-vue';
 import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
-import { TUIConstants } from '@tencentcloud/tui-core';
 import { isPC } from '../../../utils/env';
-import TUIChatConfig from '../config';
-
 const props = defineProps({
   enableSend: {
     type: Boolean,
@@ -31,8 +24,6 @@ const props = defineProps({
   },
 });
 
-const displayHover = ref(TUIChatConfig.getChatType() !== TUIConstants.TUIChat.TYPE.ROOM);
-
 const emits = defineEmits(['sendMessage']);
 
 const sendMessage = () => {

+ 72 - 78
TUIKit/components/TUIChat/message-input/message-input-editor.vue

@@ -5,10 +5,7 @@
       'message-input-container-h5': !isPC,
     }"
   >
-    <div
-      v-if="props.isMuted"
-      class="message-input-mute"
-    >
+    <div v-if="props.isMuted" class="message-input-mute">
       {{ props.muteText }}
     </div>
     <input
@@ -29,23 +26,23 @@
       @input="onInput"
       @blur="onBlur"
       @focus="onFocus"
-    >
+    />
   </div>
 </template>
 <script lang="ts" setup>
-import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
-import { TUIStore, StoreName, IConversationModel, IMessageModel } from '@tencentcloud/chat-uikit-engine';
-import { TUIGlobal } from '@tencentcloud/universal-api';
-import DraftManager from '../utils/conversationDraft';
-import { transformTextWithEmojiNamesToKeys } from '../emoji-config';
-import { isPC } from '../../../utils/env';
-import { sendMessages } from '../utils/sendMessage';
-import { ISendMessagePayload } from '../../../interface';
+import config from "@/request/config";
+import { ref, watch, onMounted, onUnmounted } from "../../../adapter-vue";
+import { TUIStore, StoreName, IConversationModel } from "@tencentcloud/chat-uikit-engine";
+import { TUIGlobal } from "@tencentcloud/universal-api";
+import { transformEmojiValueToKey } from "../utils/emojiList";
+import { isPC } from "../../../utils/env";
+import { sendMessages } from "../utils/sendMessage";
+import { ISendMessagePayload } from "../../../interface";
 
 const props = defineProps({
   placeholder: {
     type: String,
-    default: 'this is placeholder',
+    default: "this is placeholder",
   },
   replayOrReferenceMessage: {
     type: Object,
@@ -58,7 +55,7 @@ const props = defineProps({
   },
   muteText: {
     type: String,
-    default: '',
+    default: "",
   },
   enableInput: {
     type: Boolean,
@@ -76,59 +73,81 @@ const props = defineProps({
     type: Boolean,
     default: false,
   },
+  jobInfoData: {
+    type: Object,
+    default: false,
+  },
 });
 
-const emits = defineEmits(['onTyping', 'onFocus', 'onAt']);
-const inputText = ref('');
+const emits = defineEmits(["onTyping", "onFocus", "onAt"]);
+const inputText = ref("");
 const inputRef = ref();
 const inputBlur = ref(true);
 const inputContentEmpty = ref(true);
 const allInsertedAtInfo = new Map();
 const currentConversation = ref<IConversationModel>();
-const currentConversationID = ref<string>('');
-const currentQuoteMessage = ref<{ message: IMessageModel; type: string }>();
 
 onMounted(() => {
   TUIStore.watch(StoreName.CONV, {
     currentConversation: onCurrentConversationUpdated,
   });
 
-  TUIStore.watch(StoreName.CHAT, {
-    quoteMessage: onQuoteMessageUpdated,
-  });
-
-  uni.$on('insert-emoji', (data) => {
+  uni.$on("insert-emoji", (data) => {
     inputText.value += data?.emoji?.name;
   });
 
-  uni.$on('send-message-in-emoji-picker', () => {
+  uni.$on("send-message-in-emoji-picker", () => {
     handleSendMessage();
   });
 });
 
 onUnmounted(() => {
-  if (currentConversationID.value) {
-    DraftManager.setStore(currentConversationID.value, inputText.value, inputText.value, currentQuoteMessage.value);
-  }
-
-  uni.$off('insertEmoji');
-  uni.$off('send-message-in-emoji-picker');
+  uni.$off("insertEmoji");
+  uni.$off("send-message-in-emoji-picker");
 
   TUIStore.unwatch(StoreName.CONV, {
     currentConversation: onCurrentConversationUpdated,
   });
-
-  TUIStore.unwatch(StoreName.CHAT, {
-    quoteMessage: onQuoteMessageUpdated,
-  });
-
-  reset();
 });
 
 const handleSendMessage = () => {
   const messageList = getEditorContent();
   resetEditor();
-  sendMessages(messageList as any, currentConversation.value!);
+  // sendMessages(messageList as any, currentConversation.value!);  // im官方发送消息通信方法
+  // 发送消息后本地存储文本
+  let url = config.baseUrl + "/recruit/recruitCommunicate";
+  const header = {
+    "Content-Type": "application/json",
+    Authorization: uni.getStorageSync("token")
+      ? uni.getStorageSync("token")
+      : uni.getStorageSync("unitoken"),
+  };
+  let data = {
+    companyUserId: props.jobInfoData.companyUserId,
+    msgContent: {
+      text: {
+        text: messageList[0].payload.text,
+      },
+    },
+    msgType: "TIMTextElem",
+    postId: props.jobInfoData.postId,
+    recruitUserId: props.jobInfoData.recruitUserId,
+    sendType: 0,
+    status: props.jobInfoData.status,
+  };
+  let loginType = uni.getStorageSync("loginType");
+  if (loginType == 1) {
+    data.sendType = 1;
+  }
+  uni.request({
+    url: url,
+    method: "POST",
+    header: header,
+    data: data,
+    success: function (res) {
+      console.log(res.data);
+    },
+  });
 };
 
 const insertAt = (atInfo: any) => {
@@ -140,10 +159,10 @@ const insertAt = (atInfo: any) => {
 
 const getEditorContent = () => {
   let text = inputText.value;
-  text = transformTextWithEmojiNamesToKeys(text);
+  text = transformEmojiValueToKey(text);
   const atUserList: string[] = [];
   allInsertedAtInfo?.forEach((value: string, key: string) => {
-    if (text?.includes('@' + value)) {
+    if (text?.includes("@" + value)) {
       atUserList.push(key);
     }
   });
@@ -155,14 +174,14 @@ const getEditorContent = () => {
   }
   return [
     {
-      type: 'text',
+      type: "text",
       payload,
     },
   ];
 };
 
 const resetEditor = () => {
-  inputText.value = '';
+  inputText.value = "";
   inputContentEmpty.value = true;
   allInsertedAtInfo?.clear();
 };
@@ -177,7 +196,7 @@ const onBlur = () => {
 
 const onFocus = (e: any) => {
   inputBlur.value = false;
-  emits('onFocus', e?.detail?.height);
+  emits("onFocus", e?.detail?.height);
 };
 
 const isEditorContentEmpty = () => {
@@ -185,12 +204,12 @@ const isEditorContentEmpty = () => {
 };
 
 const onInput = (e: any) => {
-  // uni-app recognizes mention messages
+  // uniapp 识别 @ 消息
   const text = e?.detail?.value;
   isEditorContentEmpty();
-  if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
+  if (props.isGroup && (text.endsWith("@") || text.endsWith("@\n"))) {
     TUIGlobal?.hideKeyboard();
-    emits('onAt', true);
+    emits("onAt", true);
   }
 };
 
@@ -198,45 +217,17 @@ watch(
   () => [inputContentEmpty.value, inputBlur.value],
   (newVal: any, oldVal: any) => {
     if (newVal !== oldVal) {
-      emits('onTyping', inputContentEmpty.value, inputBlur.value);
+      emits("onTyping", inputContentEmpty.value, inputBlur.value);
     }
   },
   {
     immediate: true,
     deep: true,
-  },
+  }
 );
 
 function onCurrentConversationUpdated(conversation: IConversationModel) {
-  const prevConversationID = currentConversationID.value;
   currentConversation.value = conversation;
-  currentConversationID.value = conversation?.conversationID;
-  if (prevConversationID !== currentConversationID.value) {
-    if (prevConversationID) {
-      DraftManager.setStore(
-        prevConversationID,
-        inputText.value,
-        inputText.value,
-        currentQuoteMessage.value,
-      );
-    }
-    resetEditor();
-    if (currentConversationID.value) {
-      DraftManager.getStore(currentConversationID.value, setEditorContent);
-    }
-  }
-}
-
-function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
-  currentQuoteMessage.value = options;
-}
-
-function reset() {
-  inputBlur.value = true;
-  currentConversation.value = null;
-  currentConversationID.value = '';
-  currentQuoteMessage.value = null;
-  resetEditor();
 }
 
 defineExpose({
@@ -254,6 +245,8 @@ defineExpose({
   display: flex;
   flex-direction: column;
   flex: 1;
+  height: calc(100% - 13px);
+  width: calc(100% - 20px);
   padding: 3px 10px 10px;
   overflow: hidden;
 
@@ -261,7 +254,7 @@ defineExpose({
     flex: 1;
     height: auto;
     background: #fff;
-    border-radius: 10px;
+    border-radius: 9.4px;
     padding: 7px 0 7px 10px;
     font-size: 16px !important;
     max-height: 86px;
@@ -278,8 +271,9 @@ defineExpose({
 
   .message-input-area {
     flex: 1;
+    display: flex;
     overflow-y: scroll;
-    min-height: 25px;
+    min-height: 20px;
   }
 }
 </style>

+ 16 - 7
TUIKit/components/TUIChat/message-input/message-input-quote/index.vue

@@ -2,7 +2,7 @@
   <div
     v-if="Boolean(quoteMessage) && props.displayType !== 'audio'"
     :class="{
-      'input-quote-container': true,
+      'input-quote-content': true,
       'input-quote-container-uni': isUniFrameWork,
       'input-quote-container-h5': isH5,
     }"
@@ -33,11 +33,11 @@ import TUIChatEngine, {
 import Icon from '../../../common/Icon.vue';
 import closeIcon from '../../../../assets/icon/icon-close.svg';
 import { isH5, isUniFrameWork } from '../../../../utils/env';
-import { transformTextWithKeysToEmojiNames } from '../../emoji-config';
+import { decodeTextMessage } from '../../utils/emojiList';
 import { InputDisplayType } from '../../../../interface';
 
 interface IProps {
-  displayType?: InputDisplayType;
+  displayType: InputDisplayType;
 }
 
 const props = withDefaults(defineProps<IProps>(), {
@@ -51,19 +51,27 @@ onMounted(() => {
   TUIStore.watch(StoreName.CHAT, {
     quoteMessage: onQuoteMessageUpdated,
   });
+
+  TUIStore.watch(StoreName.CONV, {
+    currentConversationID: onConversationIDUpdated,
+  });
 });
 
 onUnmounted(() => {
   TUIStore.unwatch(StoreName.CHAT, {
     quoteMessage: onQuoteMessageUpdated,
   });
+
+  TUIStore.unwatch(StoreName.CONV, {
+    currentConversationID: onConversationIDUpdated,
+  });
 });
 
 const quoteContentText = computed(() => {
   let _quoteContentText;
   switch (quoteMessage.value?.type) {
     case TYPES.MSG_TEXT:
-      _quoteContentText = transformTextWithKeysToEmojiNames(quoteMessage.value.payload?.text);
+      _quoteContentText = decodeTextMessage(quoteMessage.value.payload?.text);
       break;
     case TYPES.MSG_IMAGE:
       _quoteContentText = TUITranslateService.t('TUIChat.图片');
@@ -83,9 +91,6 @@ const quoteContentText = computed(() => {
     case TYPES.MSG_FACE:
       _quoteContentText = TUITranslateService.t('TUIChat.表情');
       break;
-    case TYPES.MSG_MERGER:
-      _quoteContentText = TUITranslateService.t('TUIChat.聊天记录');
-      break;
     default:
       _quoteContentText = TUITranslateService.t('TUIChat.消息');
       break;
@@ -104,6 +109,10 @@ function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string
     quoteMessage.value = undefined;
   }
 }
+
+function onConversationIDUpdated() {
+  cancelQuote();
+}
 </script>
 
 <style lang="scss" scoped>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 483 - 480
TUIKit/components/TUIChat/message-list/index.vue


+ 297 - 0
TUIKit/components/TUIChat/message-list/interview-application.vue

@@ -0,0 +1,297 @@
+<template>
+  <div>
+    <BottomPopup
+      :show="showDialog"
+      @touchmove.stop.prevent
+      @onClose="onPopupClose"
+      :borderRadius="12"
+    >
+      <div :class="['evaluate', !isPC && 'evaluate-h5']">
+        <div :class="['evaluate-header', !isPC && 'evaluate-h5-header']">
+          <div
+            :class="['evaluate-header-content', !isPC && 'evaluate-h5-header-content']"
+          >
+            面试预约
+          </div>
+        </div>
+        <div
+          v-if="!isPC"
+          :class="['evaluate-header-close', !isPC && 'evaluate-h5-header-close']"
+          @click="ShowDialogClose"
+        >
+          <image
+            src="@/static/images/company/close.png"
+            mode=""
+            style="width: 30px; height: 30px"
+          />
+        </div>
+        <div :class="['evaluate-content', !isPC && 'evaluate-h5-content']">
+          <div class="item">
+            <div class="label">预约日期:</div>
+            <picker
+              class="picker"
+              mode="date"
+              :value="params.date"
+              @change="bindDateChange"
+            >
+              <view class="uni-input">{{
+                params.date ? params.date : "请选择预约日期"
+              }}</view>
+            </picker>
+          </div>
+          <div class="item">
+            <div class="label">预约时间:</div>
+            <picker
+              class="picker"
+              mode="time"
+              :value="params.time"
+              @change="bindTimeChange"
+            >
+              <view class="uni-input">{{
+                params.time ? params.time : "请选择预约时间"
+              }}</view>
+            </picker>
+          </div>
+
+          <div class="item">
+            <div class="label">面试地址:</div>
+            <input
+              @tap="inputClick"
+              style="
+                height: 40px;
+                width: 94%;
+                background: #f8f8f8;
+                text-align: left;
+                line-height: 39px;
+                padding: 0px 11px;
+              "
+              @input="replaceInputPerson"
+              :value="params.interviewAddress"
+              focus
+              placeholder="请选择面试地址"
+            />
+          </div>
+
+          <div class="item">
+            <div class="label">联系人:</div>
+            <input
+              style="
+                height: 40px;
+                width: 94%;
+                background: #f8f8f8;
+                text-align: left;
+                line-height: 39px;
+                padding: 0px 11px;
+              "
+              @input="replaceInputPerson"
+              :value="params.contact"
+              focus
+              placeholder="联系人"
+            />
+          </div>
+          <div class="item">
+            <div class="label">联系电话:</div>
+            <input
+              style="
+                height: 40px;
+                width: 94%;
+                background: #f8f8f8;
+                text-align: left;
+                line-height: 39px;
+                padding: 0px 11px;
+              "
+              @input="replaceInputPhone"
+              :value="params.contactMobile"
+              focus
+              placeholder="联系电话"
+            />
+          </div>
+          <div class="item">
+            <div class="label">信息费:</div>
+            <div style="color: red">{{ userInfo.hc ? userInfo.hc : 0 }}元</div>
+          </div>
+          <div
+            :class="['evaluate-content-button', !isPC && 'evaluate-h5-content-button']"
+          >
+            <button :class="['btn', 'btn-valid']" @click="submitEvaluate">确定</button>
+          </div>
+        </div>
+      </div>
+    </BottomPopup>
+  </div>
+</template>
+
+<script>
+import {
+  TUITranslateService,
+  TUIStore,
+  StoreName,
+  IConversationModel,
+  TUIChatService,
+  SendMessageParams,
+} from "@tencentcloud/chat-uikit-engine";
+import config from "@/request/config";
+import BottomPopup from "../../../components/common/BottomPopup/indexYQ.vue";
+export default {
+  components: { BottomPopup },
+  props: ["showDialog", "jobInfoData", "mapAddress"],
+  data() {
+    return {
+      isPC: false,
+      inpvalue: "",
+      comment: "",
+      params: {
+        date: "",
+        time: "",
+        companyUserId: "",
+        contact: "",
+        contactMobile: "",
+        interviewAddress: "",
+        interviewStatus: "0",
+        interviewTime: "",
+        postId: "",
+        recruitUserId: "",
+        lat: "",
+        lng: "",
+      },
+      userInfo: {},
+      addressObj: {},
+    };
+  },
+  watch: {
+    jobInfoData: {
+      handler(val) {
+        this.userInfo = val;
+      },
+      immediate: true,
+      deep: true,
+    },
+    mapAddress: {
+      handler(val) {
+        this.addressObj = val;
+        let list = this.addressObj.location.split(",");
+        this.params.interviewAddress =
+          this.addressObj.district + this.addressObj.address + this.addressObj.name;
+        this.params.lng = list[0];
+        this.params.lat = list[1];
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  methods: {
+    inputClick() {
+      uni.navigateTo({
+        url: `/pages/merChantSide/map`,
+      });
+    },
+    replaceInputPerson(val) {
+      this.params.contact = val.detail.value;
+    },
+    replaceInputPhone(val) {
+      this.params.contactMobile = val.detail.value;
+    },
+    bindDateChange(val) {
+      this.params.date = val.detail.value;
+    },
+    bindTimeChange(val) {
+      this.params.time = val.detail.value;
+    },
+    ShowDialogClose() {
+      this.$emit("close", false);
+    },
+    submitEvaluate() {
+      let that = this;
+      this.params.companyUserId = uni.getStorageSync("userId");
+      this.params.recruitUserId = this.userInfo.recruitUserId;
+      this.params.interviewTime = this.params.date + " " + this.params.time;
+      this.params.postId = this.userInfo.postId;
+      this.params.resumeId = this.userInfo.resumeId;
+      const header = {
+        "Content-Type": "application/json",
+        Authorization: uni.getStorageSync("token")
+          ? uni.getStorageSync("token")
+          : uni.getStorageSync("unitoken"),
+      };
+      that.$emit("onload", true);
+      that.$emit("close", false);
+      uni.request({
+        url: config.baseUrl + "/recruit/companyInterview/invite",
+        method: "post",
+        header: header,
+        data: that.params,
+        //请求成功后返回
+        success: (res) => {
+          console.log(res);
+          if (res.data.code == 200) {
+            uni.$u.toast("发送成功!");
+            that.params.interviewAddress = "";
+          } else {
+            uni.$u.toast(res.data.msg);
+          }
+        },
+        fail: (error) => {
+          console.log(error);
+          uni.showToast({
+            title: error.data.msg,
+            //显示持续时间为 2秒
+            duration: 2000,
+          });
+        },
+      });
+      that.$emit("onload", true);
+      that.$emit("close", false);
+    },
+  },
+};
+</script>
+<style scoped lang="scss">
+.picker {
+  height: 40px;
+  width: 94%;
+  background: #f8f8f8;
+  text-align: left;
+  line-height: 39px;
+  padding: 0px 11px;
+}
+.item {
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 10px;
+  .label {
+    width: 100px;
+  }
+}
+.evaluate-h5 {
+  padding: 0px !important;
+}
+.evaluate-header {
+  background: url("../../../../static/images/company/popupHeader.png") no-repeat;
+  background-size: 100% 100%;
+  padding: 20px;
+}
+.evaluate-h5-content {
+  padding: 20px !important;
+}
+.evaluate-h5-header {
+  display: flex;
+  justify-content: center !important;
+}
+.evaluate-h5-header-close {
+  position: absolute;
+  right: 10px;
+  top: 8px;
+}
+.btn-valid {
+  background: linear-gradient(90deg, #02d17e 0%, #00b594 100%);
+  border-radius: 12rpx;
+}
+</style>
+
+<style
+  scoped
+  lang="scss"
+  src="../../../components/TUIChat/message-input-toolbar/evaluate/style/index.scss"
+></style>

+ 4 - 4
TUIKit/components/TUIChat/message-list/message-elements/message-audio.vue

@@ -44,7 +44,6 @@ interface IEmits {
     map: Map<string, IAudioContext>,
     options?: { newAudioSrc: string }
   ): void;
-  (e: 'setAudioPlayed', messageID: string): void;
 }
 
 const emits = defineEmits<IEmits>();
@@ -68,6 +67,7 @@ onUnmounted(() => {
 watch(() => props.broadcastNewAudioSrc, (newSrc) => {
   if (newSrc !== props.content.url && isAudioPlaying.value) {
     stopAudio();
+    // 这一步的 audioContext 大概率已被销毁 手动执行一下暂停
     // The audioContext may have been destroyed. Manually execute the pause
     isAudioPlaying.value = false;
   }
@@ -81,6 +81,7 @@ function toggleClick() {
     });
     return;
   }
+  // audioContext 会被缓存 必须拉取一次
   // audioContext will be cached, it must be get first
   const audioContext = getAudio();
   if (!audioContext) {
@@ -122,9 +123,6 @@ function playAudio() {
     return;
   }
   audioContext.play();
-  if (props.messageItem.flow === 'in') {
-    emits('setAudioPlayed', props.messageItem.ID);
-  }
 }
 
 function stopAudio() {
@@ -133,6 +131,7 @@ function stopAudio() {
     return;
   }
   try {
+    // audiocontext 的内存对象还在 但可能播放实例已被销毁
     // The memory of the audiocontext is in memory. But The play instance may have been destroyed.
     audioContext.stop();
   } catch {
@@ -180,6 +179,7 @@ $flow-out-bg-color: #dceafd;
   overflow: hidden;
 
   .audio-icon-container {
+    // uniapp中会有一些偏差 这里 width 要比图标略宽
     width: 16px;
     height: 20px;
     position: relative;

+ 195 - 419
TUIKit/components/TUIChat/message-list/message-elements/message-bubble.vue

@@ -1,260 +1,144 @@
 <template>
   <div :class="containerClassNameList">
-    <!-- multiple select radio -->
-    <RadioSelect
-      v-if="props.isMultipleSelectMode"
-      class="multiple-select-radio"
-      :isSelected="isMultipleSelected"
-      @onChange="toggleMultipleSelect"
-    />
+    <!-- todo 统一组件处理-->
     <div
-      :class="{
-        'control-reverse': message.flow === 'out',
-      }"
+      class="message-bubble-main-content"
+      :class="[message.flow === 'in' ? '' : 'reverse']"
     >
-      <!-- message-bubble-container -->
-      <div class="message-bubble-content">
+      <Avatar
+        useSkeletonAnimation
+        :url="message.avatar || ''"
+      />
+      <main
+        class="message-body"
+        @click.stop
+      >
         <div
-          class="message-bubble-main-content"
-          :class="[message.flow === 'in' ? '' : 'reverse']"
+          v-if="message.flow === 'in' && message.conversationType === 'GROUP'"
+          class="message-body-nick-name"
         >
-          <Avatar
-            useSkeletonAnimation
-            :url="message.avatar || ''"
-            :style="{ flex: '0 0 auto' }"
-          />
-          <main class="message-body" @click.stop>
-            <div
-              v-if="
-                message.flow === 'in' && message.conversationType === 'GROUP'
-              "
-              class="message-body-nick-name"
-            >
-              {{ props.content.showName }}
+          {{ props.content.showName }}
+        </div>
+        <div :class="['message-body-main', message.flow === 'out' && 'message-body-main-reverse']">
+          <div
+            :class="[
+              'blink',
+              'message-body-content',
+              message.flow === 'out' ? 'content-out' : 'content-in',
+              message.hasRiskContent && 'content-has-risk',
+              isNoPadding ? 'content-no-padding' : '',
+              isNoPadding && isBlink ? 'blink-shadow' : '',
+              !isNoPadding && isBlink ? 'blink-content' : '',
+            ]"
+          >
+            <div class="content-main">
+              <img
+                v-if="
+                  (message.type === TYPES.MSG_IMAGE || message.type === TYPES.MSG_VIDEO) &&
+                    message.hasRiskContent
+                "
+                :class="['message-risk-replace', !isPC && 'message-risk-replace-h5']"
+                :src="riskImageReplaceUrl"
+              >
+              <template v-else>
+                <slot />
+              </template>
             </div>
+            <!-- 敏感信息失败提示 -->
             <div
-              :class="[
-                'message-body-main',
-                message.flow === 'out' && 'message-body-main-reverse',
-              ]"
+              v-if="message.hasRiskContent"
+              class="content-has-risk-tips"
             >
-              <div
-                :class="[
-                  'blink',
-                  'message-body-content',
-                  messageClass(message),
-
-                  message.hasRiskContent && 'content-has-risk',
-                  isNoPadding ? 'content-no-padding' : '',
-                  isNoPadding && isBlink ? 'blink-shadow' : '',
-                  !isNoPadding && isBlink ? 'blink-content' : '',
-                ]"
-              >
-                <div class="content-main">
-                  <img
-                    v-if="
-                      (message.type === TYPES.MSG_IMAGE ||
-                        message.type === TYPES.MSG_VIDEO) &&
-                      message.hasRiskContent
-                    "
-                    :class="[
-                      'message-risk-replace',
-                      !isPC && 'message-risk-replace-h5',
-                    ]"
-                    :src="riskImageReplaceUrl"
-                  />
-                  <template v-else>
-                    <slot />
-                  </template>
-                </div>
-                <!-- Risk Content Tips -->
-                <div
-                  v-if="message.hasRiskContent"
-                  class="content-has-risk-tips"
-                >
-                  {{ riskContentText }}
-                </div>
-              </div>
-
-              <!-- audio unplay mark -->
-              <div v-if="isDisplayUnplayMark" class="audio-unplay-mark" />
-              <!-- Fail Icon -->
-              <div
-                v-if="message.status === 'fail' || message.hasRiskContent"
-                class="message-label fail"
-                @click="resendMessage()"
-              >
-                !
-              </div>
-              <!-- Loading Icon -->
-              <Icon
-                v-if="
-                  message.status === 'unSend' &&
-                  needLoadingIconMessageType.includes(message.type)
-                "
-                class="message-label loading-circle"
-                :file="loadingIcon"
-                :width="'15px'"
-                :height="'15px'"
-              />
-              <!-- Read & Unread -->
-              <ReadStatus
-                class="message-label align-self-bottom"
-                :message="shallowCopyMessage(message)"
-                @openReadUserPanel="openReadUserPanel"
-              />
+              {{ riskContentText }}
             </div>
-          </main>
-        </div>
-        <!-- message extra area -->
-        <div class="message-bubble-extra-content">
-          <!-- extra: message translation -->
-          <MessageTranslate
-            :class="message.flow === 'out' ? 'reverse' : 'flex-row'"
-            :message="message"
+          </div>
+          <!-- 发送失败 -->
+          <div
+            v-if="message.status === 'fail' || message.hasRiskContent"
+            class="message-label fail"
+            @click="resendMessage()"
+          >
+            !
+          </div>
+          <!-- 加载图标 -->
+          <Icon
+            v-if="message.status === 'unSend' && needLoadingIconMessageType.includes(message.type)"
+            class="message-label loading-circle"
+            :file="loadingIcon"
+            :width="'15px'"
+            :height="'15px'"
           />
-          <!-- extra: message convert voice to text -->
-          <MessageConvert
-            :class="message.flow === 'out' ? 'reverse' : 'flex-row'"
-            :message="message"
-          />
-          <!-- extra: message quote -->
-          <MessageQuote
-            :class="message.flow === 'out' ? 'reverse' : 'flex-row'"
-            :message="message"
-            @blinkMessage="blinkMessage"
-            @scrollTo="scrollTo"
+          <!-- 已读 & 未读 -->
+          <ReadStatus
+            class="message-label align-self-bottom"
+            :message="shallowCopyMessage(message)"
+            @openReadUserPanel="openReadUserPanel"
           />
         </div>
-      </div>
+      </main>
+    </div>
+    <!-- message extra area -->
+    <div class="message-bubble-extra-content">
+      <!-- extra: message translation -->
+      <MessageTranslate
+        :class="message.flow === 'out' ? 'reverse' : 'flex-row'"
+        :message="message"
+      />
+      <!-- extra: message convert voice to text -->
+      <MessageConvert
+        :class="message.flow === 'out' ? 'reverse' : 'flex-row'"
+        :message="message"
+      />
+      <!-- extra: message quote -->
+      <MessageQuote
+        :class="message.flow === 'out' ? 'reverse' : 'flex-row'"
+        :message="message"
+        @blinkMessage="blinkMessage"
+        @scrollTo="scrollTo"
+      />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, toRefs } from "../../../../adapter-vue";
-import TUIChatEngine, {
-  TUITranslateService,
-  IMessageModel,
-} from "@tencentcloud/chat-uikit-engine";
-import Icon from "../../../common/Icon.vue";
-import ReadStatus from "./read-status/index.vue";
-import MessageQuote from "./message-quote/index.vue";
-import Avatar from "../../../common/Avatar/index.vue";
-import MessageTranslate from "./message-translate/index.vue";
-import MessageConvert from "./message-convert/index.vue";
-import RadioSelect from "../../../common/RadioSelect/index.vue";
-import loadingIcon from "../../../../assets/icon/loading.png";
-import { shallowCopyMessage } from "../../utils/utils";
-import { isPC } from "../../../../utils/env";
-import { watchEffect, ref } from "../../../../adapter-vue";
-import { isUrl, JSONToObject } from "../../../../utils/index";
-import moment from "moment";
+import { computed, toRefs } from '../../../../adapter-vue';
+import TUIChatEngine, { TUITranslateService, IMessageModel } from '@tencentcloud/chat-uikit-engine';
+import Icon from '../../../common/Icon.vue';
+import ReadStatus from './read-status/index.vue';
+import MessageQuote from './message-quote/index.vue';
+import Avatar from '../../../common/Avatar/index.vue';
+import MessageTranslate from './message-translate/index.vue';
+import MessageConvert from './message-convert/index.vue';
+import loadingIcon from '../../../../assets/icon/loading.png';
+import { shallowCopyMessage } from '../../utils/utils';
+import { isPC } from '../../../../utils/env';
+
 interface IProps {
   messageItem: IMessageModel;
   content?: any;
-  classNameList?: string[];
   blinkMessageIDList?: string[];
-  isMultipleSelectMode?: boolean;
-  isAudioPlayed?: boolean | undefined;
-  multipleSelectedMessageIDList?: string[];
+  classNameList?: string[];
 }
 
 interface IEmits {
-  (e: "resendMessage"): void;
-  (e: "blinkMessage", messageID: string): void;
-  (
-    e: "setReadReceiptPanelVisible",
-    visible: boolean,
-    message?: IMessageModel
-  ): void;
-  (
-    e: "changeSelectMessageIDList",
-    options: { type: "add" | "remove" | "clearAll"; messageID: string }
-  ): void;
-  // Only for uni-app
-  (e: "scrollTo", scrollHeight: number): void;
+  (e: 'resendMessage'): void;
+  (e: 'blinkMessage', messageID: string): void;
+  (e: 'setReadReceiptPanelVisible', visible: boolean, message?: IMessageModel): void;
+  // 下面的方法是 uniapp 专属
+  (e: 'scrollTo', scrollHeight: number): void;
 }
 
 const emits = defineEmits<IEmits>();
-const isCustom = ref();
-watchEffect(() => {
-  const { payload } = props.messageItem;
-  isCustom.value = payload.data || "";
-  isCustom.value = JSONToObject(payload.data);
-});
-function messageClass(message) {
-  // 从消息负载数据中提取 businessId
-  const businessId =
-    message._message &&
-    message._message.payload &&
-    message._message.payload.data
-      ? JSON.parse(message._message.payload.data).businessID
-      : "";
-  const msgtime =
-    message._message &&
-    message._message.payload &&
-    message._message.payload.data
-      ? JSON.parse(message._message.payload.data).expireTime
-      : "";
-
-  let expireTime = moment(msgtime).format("YYYY-MM-DD");
-  let expired = false;
-  const now = moment();
-  if (expireTime && now.isAfter(expireTime)) {
-    expired = true;
-  } else if (expireTime) {
-    expired = false;
-  }
-
-  // 判断消息流向是 "out"
-  if (message.flow === "out") {
-    // 判断 businessId 是否为红包消息相关
-    if (businessId === "red_envelope_message") {
-      if (expired) {
-        return "contentCustom-out-lq";
-      } else {
-        return "contentCustom-out";
-      }
-    } else if (businessId === "red_envelope_message_gq") {
-      return "contentCustom-out-lq";
-    } else {
-      return "content-out";
-    }
-  }
-  // 判断消息流向是 "in"
-  else if (message.flow === "in") {
-    // 判断 businessId 是否为红包消息相关
-    if (businessId === "red_envelope_message") {
-      if (expired) {
-        return "contentCustom-in-lq";
-      } else {
-        return "contentCustom-in";
-      }
-    } else if (businessId === "red_envelope_message_gq") {
-      return "contentCustom-in-lq";
-    } else {
-      return "content-in";
-    }
-  }
-
-  // 如果消息流向不是 "in" 或 "out",返回空字符串
-  return "";
-}
 
 const props = withDefaults(defineProps<IProps>(), {
-  isAudioPlayed: false,
   messageItem: () => ({} as IMessageModel),
   content: () => ({}),
   blinkMessageIDList: () => [],
   classNameList: () => [],
-  isMultipleSelectMode: false,
-  multipleSelectedMessageIDList: () => [],
 });
 
 const TYPES = TUIChatEngine.TYPES;
-const riskImageReplaceUrl =
-  "https://web.sdk.qcloud.com/component/TUIKit/assets/has_risk_default.png";
+const riskImageReplaceUrl = 'https://web.sdk.qcloud.com/component/TUIKit/assets/has_risk_default.png';
 const needLoadingIconMessageType = [
   TYPES.MSG_LOCATION,
   TYPES.MSG_TEXT,
@@ -265,42 +149,21 @@ const needLoadingIconMessageType = [
 
 const { blinkMessageIDList, messageItem: message } = toRefs(props);
 
-const isMultipleSelected = computed<boolean>(() => {
-  return props.multipleSelectedMessageIDList.includes(message.value.ID);
-});
-
-const isDisplayUnplayMark = computed<boolean>(() => {
-  return (
-    message.value.flow === "in" &&
-    message.value.status === "success" &&
-    message.value.type === TYPES.MSG_AUDIO &&
-    !props.isAudioPlayed
-  );
-});
-
 const containerClassNameList = computed(() => {
-  return [
-    "message-bubble",
-    isMultipleSelected.value ? "multiple-selected" : "",
-    ...props.classNameList,
-  ];
+  return ['message-bubble', ...props.classNameList];
 });
 
 const isNoPadding = computed(() => {
-  return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO, TYPES.MSG_MERGER].includes(
-    message.value.type
-  );
+  return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO].includes(message.value.type);
 });
 
 const riskContentText = computed<string>(() => {
-  let content = TUITranslateService.t("TUIChat.涉及敏感内容") + ", ";
-  if (message.value.flow === "out") {
-    content += TUITranslateService.t("TUIChat.发送失败");
+  let content = TUITranslateService.t('TUIChat.涉及敏感内容') + ', ';
+  if (message.value.flow === 'out') {
+    content += TUITranslateService.t('TUIChat.发送失败');
   } else {
     content += TUITranslateService.t(
-      message.value.type === TYPES.MSG_AUDIO
-        ? "TUIChat.无法收听"
-        : "TUIChat.无法查看"
+      message.value.type === TYPES.MSG_AUDIO ? 'TUIChat.无法收听' : 'TUIChat.无法查看',
     );
   }
   return content;
@@ -313,82 +176,26 @@ const isBlink = computed(() => {
   return false;
 });
 
-function toggleMultipleSelect(isSelected: boolean) {
-  emits("changeSelectMessageIDList", {
-    type: isSelected ? "add" : "remove",
-    messageID: message.value.ID,
-  });
-}
-
 function resendMessage() {
   if (!message.value?.hasRiskContent) {
-    emits("resendMessage");
+    emits('resendMessage');
   }
 }
 
 function blinkMessage(messageID: string) {
-  emits("blinkMessage", messageID);
+  emits('blinkMessage', messageID);
 }
 
 function scrollTo(scrollHeight: number) {
-  emits("scrollTo", scrollHeight);
+  emits('scrollTo', scrollHeight);
 }
 
 function openReadUserPanel() {
-  emits("setReadReceiptPanelVisible", true, message.value);
+  emits('setReadReceiptPanelVisible', true, message.value);
 }
 </script>
 
 <style lang="scss" scoped>
-.contentCustom-out{
-  height: 100px;
-  // background: linear-gradient(225deg, #f74d30 0%, #ff7633 100%);
-  background: url("../../../../../static/img/fshb.png") no-repeat;
-  background-size: 100% 100%;
-  padding: 16px 20px !important;
-  display: block !important;
-  box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(226, 226, 226, 0.23);
-  border-radius: 20px 0px 20px 20px;
-}
-.contentCustom-in {
-  height: 100px;
-  // background: linear-gradient(225deg, #f74d30 0%, #ff7633 100%);
-  background: url("../../../../../static/img/hbbj.png") no-repeat;
-  background-size: 100% 100%;
-  padding: 16px 20px !important;
-  display: block !important;
-  box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(226, 226, 226, 0.23);
-  border-radius: 0px 20px 20px 20px;
-}
-.contentCustom-in-lq {
-  opacity: 0.5;
-  height: 100px;
-  // background: linear-gradient(225deg, #f74d30 0%, #ff7633 100%);
-  background: url("../../../../../static/img/hbbj.png") no-repeat;
-  background-size: 100% 100%;
-  padding: 16px 20px !important;
-  display: block !important;
-  box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(226, 226, 226, 0.23);
-  border-radius: 0px 20px 20px 20px;
-}
-.contentCustom-out-lq {
-  opacity: 0.5;
-  height: 100px;
-  // background: linear-gradient(225deg, #f74d30 0%, #ff7633 100%);
-  background: url("../../../../../static/img/fshb.png") no-repeat;
-  background-size: 100% 100%;
-  padding: 16px 20px !important;
-  display: block !important;
-  box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(226, 226, 226, 0.23);
-  border-radius: 20px 0px 20px 20px;
-}
-:not(not) {
-  display: flex;
-  flex-direction: column;
-  min-width: 0;
-  box-sizing: border-box;
-}
-
 .flex-row {
   display: flex;
 }
@@ -400,33 +207,19 @@ function openReadUserPanel() {
 }
 
 .message-bubble {
-  padding: 10px 15px;
+  width: 100%;
+  box-sizing: border-box;
   display: flex;
-  flex-direction: row;
+  flex-direction: column;
+  padding: 0 20px 25px;
   user-select: none;
-  -webkit-touch-callout: none;
-  -webkit-user-select: none;
-  -khtml-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-
-  &.multiple-selected {
-    background-color: #f0f0f0;
-  }
-
-  .multiple-select-radio {
-    margin-right: 12px;
-    flex: 0 0 auto;
-  }
-
-  .control-reverse {
-    flex: 1 1 auto;
-    flex-direction: row-reverse;
-  }
-
+  -webkit-touch-callout: none; /* 系统默认菜单被禁用 */
+  -webkit-user-select: none; /* webkit浏览器 */
+  -khtml-user-select: none; /* 早期浏览器 */
+  -moz-user-select: none;/* 火狐 */
+  -ms-user-select: none; /* IE10 */
   .message-bubble-main-content {
     display: flex;
-    flex-direction: row;
 
     .message-avatar {
       display: block;
@@ -442,9 +235,9 @@ function openReadUserPanel() {
       flex-direction: column;
       align-items: flex-start;
       margin: 0 8px;
+      max-width: calc(100% - 54px);
 
       .message-body-nick-name {
-        display: block;
         margin-bottom: 4px;
         font-size: 12px;
         color: #999;
@@ -465,15 +258,6 @@ function openReadUserPanel() {
           flex-direction: row-reverse;
         }
 
-        .audio-unplay-mark {
-          flex: 0 0 auto;
-          width: 5px;
-          height: 5px;
-          border-radius: 50%;
-          background-color: #f00;
-          margin: 5px;
-        }
-
         .message-body-content {
           display: flex;
           flex-direction: column;
@@ -511,107 +295,99 @@ function openReadUserPanel() {
             margin-top: 5px;
             border-top: 1px solid #e5c7c7;
             padding-top: 5px;
-          }
         }
+      }
 
-        .content-in {
-          // background: #fbfbfb;
-          background: linear-gradient(180deg, #d0fbdd 0%, #ffffff 100%);
-          border-radius: 0 10px 10px;
-        }
+      .content-in {
+        background: #fbfbfb;
+        border-radius: 0 10px 10px;
+      }
 
-        .content-out {
-          background: #00d36d;
-          border-radius: 10px 0 10px 10px;
-          color: white;
-        }
+      .content-out {
+        background: #fff;
+        border-radius: 10px 0 10px 10px;
+      }
 
-        .content-no-padding {
-          padding: 0;
-          background: transparent;
-          border-radius: 10px;
-          overflow: hidden;
-        }
+      .content-no-padding {
+        padding: 0;
+        background: transparent;
+        border-radius: 10px;
+        overflow: hidden;
+      }
 
-        .content-no-padding.content-has-risk {
-          padding: 12px;
-        }
+      .content-no-padding.content-has-risk {
+        padding: 12px;
+      }
 
-        .content-has-risk {
-          background: rgba(250, 81, 81, 0.16);
-        }
+      .content-has-risk {
+        background: rgba(250, 81, 81, 0.16);
+      }
 
-        .blink-shadow {
-          @keyframes shadow-blink {
-            50% {
-              box-shadow: rgba(255, 156, 25, 1) 0 0 10px 0;
-            }
+      .blink-shadow {
+        @keyframes shadow-blink {
+          50% {
+            box-shadow: rgba(255, 156, 25, 1) 0 0 10px 0;
           }
-
-          box-shadow: rgba(255, 156, 25, 0) 0 0 10px 0;
-          animation: shadow-blink 1s linear 3;
         }
 
-        .blink-content {
-          @keyframes reference-blink {
-            50% {
-              background-color: #ff9c19;
-            }
-          }
+        box-shadow: rgba(255, 156, 25, 0) 0 0 10px 0;
+        animation: shadow-blink 1s linear 3;
+      }
 
-          animation: reference-blink 1s linear 3;
+      .blink-content {
+        @keyframes reference-blink {
+          50% {
+            background-color: #ff9c19;
+          }
         }
 
-        .message-label {
-          align-self: flex-end;
-          font-family: PingFangSC-Regular;
-          font-size: 12px;
-          color: #b6b8ba;
-          word-break: keep-all;
-          flex: 0 0 auto;
-          margin: 0 8px;
-
-          &.fail {
-            width: 15px;
-            height: 15px;
-            border-radius: 15px;
-            background: red;
-            color: #fff;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            cursor: pointer;
-          }
+        animation: reference-blink 1s linear 3;
+      }
 
-          &.loading-circle {
-            opacity: 0;
-            animation: circle-loading 2s linear 1s infinite;
-          }
+    .message-label {
+      align-self: flex-end;
+      font-family: PingFangSC-Regular;
+      font-size: 12px;
+      color: #b6b8ba;
+      word-break: keep-all;
+      flex: 0 0 auto;
+      margin: 0 8px;
 
-          @keyframes circle-loading {
-            0% {
-              transform: rotate(0);
-              opacity: 1;
-            }
+      &.fail {
+        width: 15px;
+        height: 15px;
+        border-radius: 15px;
+        background: red;
+        color: #fff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+      }
 
-            100% {
-              opacity: 1;
-              transform: rotate(360deg);
-            }
-          }
+      &.loading-circle {
+        opacity: 0;
+        animation: circle-loading 2s linear 1s infinite;
+      }
+
+      @keyframes circle-loading {
+        0% {
+          transform: rotate(0);
+          opacity: 1;
         }
 
-        .align-self-bottom {
-          align-self: flex-end;
+        100% {
+          opacity: 1;
+          transform: rotate(360deg);
         }
       }
     }
-  }
 
-  .reverse {
-    display: flex;
-    flex-direction: row-reverse;
-    justify-content: flex-start;
+    .align-self-bottom {
+      align-self: flex-end;
+        }
+      }
+    }
   }
 
   .message-bubble-extra-content {

+ 139 - 207
TUIKit/components/TUIChat/message-list/message-elements/message-custom.vue

@@ -1,41 +1,56 @@
 <template>
-  <div class="custom">
-    <template v-if="isCustom.businessID === 'red_envelope_message'">
-      <div class="evaluate" @click="lqhb(isCustom)">
-        <div class="evaluatered">
-          {{ titleImpl(isCustom.expireTime) }}
-        </div>
-        <div class="time">
-          {{ timeImpl(isCustom.expireTime) }}
+  <div class="custom" style="font-size: 14px">
+    <!-- 发送联系方式 -->
+    <template v-if="isCustom.businessID === CHAT_MSG_CUSTOM_TYPE.PHONE">
+      <div class="phone">
+        <img
+          src="https://qcloudimg.tencent-cloud.cn/trisys/assets/product/images/SOOZNXCHkHcm50wX2ndp4.png"
+          style="width: 50px; height: 50px; border-radius: 10px"
+        />
+        <div style="margin-left: 10px; line-height: 30px">
+          <p>{{ isCustom.title }}</p>
+          <p id="copyPhone">{{ isCustom.iphone }}</p>
         </div>
       </div>
-    </template>
-    <template v-else-if="isCustom.businessID === 'red_envelope_message_gq'">
-      <div class="evaluate">
-        <div class="evaluatered">通用红包</div>
-        <div class="time">已领取</div>
+      <div class="xian"></div>
+      <div class="phone_footer">
+        <span> <a :href="`tel:` + isCustom.iphone" ref="telPhone">拨号</a></span>
+        <span>复制</span>
+        <!-- <span @tap="PhoneChange('2', isCustom)"> 发短信</span> -->
       </div>
     </template>
-
-    <template v-else-if="isCustom.businessID === 'order'">
-      <div class="evaluate">
-        <div @click="product(isCustom)">
-          <div class="left">
-            <img
-              :src="isCustom.imageUrl"
-              style="width: 80px; height: 80px; border-radius: 10px"
-            />
-            <div class="right">
-              <div class="label">
-                {{ isCustom.title }}
-              </div>
-              <div class="label">描述:{{ isCustom.spuDesc }}</div>
-              <div style="font-weight: 400; font-size: 28rpx">
-                价格:{{ isCustom.price.toFixed(2) }}元/{{ isCustom.unit }}
-              </div>
-            </div>
+    <!-- 面试邀请 -->
+    <template v-else-if="isCustom.businessID === CHAT_MSG_CUSTOM_TYPE.ORDER">
+      <div class="order">
+        <div class="header">
+          <img :src="isCustom.imageUrl" />
+          <div class="main">
+            <h1>您已发起面试邀请</h1>
+            <span>云赋能网络信息有限公司</span>
           </div>
         </div>
+        <div class="xian"></div>
+        <div class="contont">
+          <div>
+            <p class="title">面试时间</p>
+            <p class="value">{{ isCustom.interviewtime }}</p>
+          </div>
+          <div>
+            <p class="title">面试地点</p>
+            <p class="value">{{ isCustom.Interviewlocation }}</p>
+          </div>
+          <div>
+            <p class="title">联系方式</p>
+            <p class="value">{{ isCustom.name }}{{ isCustom.iphone }}</p>
+          </div>
+        </div>
+        <div class="xian"></div>
+        <div class="phone_footer">
+          <span style="color: #04c4ab">信息费:4HC</span>
+          <span>等待接受</span>
+          <!-- <span v-else style="color: #0776eb" @click="AcceptInvitation">接受邀请</span> -->
+          <!-- <span @tap="PhoneChange('2', isCustom)"> 发短信</span> -->
+        </div>
       </div>
     </template>
     <template v-else>
@@ -46,162 +61,98 @@
 
 <script lang="ts" setup>
 import { watchEffect, ref } from "../../../../adapter-vue";
-import { Toast, TOAST_TYPE } from "../../../common/Toast/index";
-import {
-  TUITranslateService,
-  IMessageModel,
-  TUIStore,
-} from "@tencentcloud/chat-uikit-engine";
+import { TUITranslateService, IMessageModel } from "@tencentcloud/chat-uikit-engine";
 import { isUrl, JSONToObject } from "../../../../utils/index";
 import { CHAT_MSG_CUSTOM_TYPE } from "../../../../constant";
 import { ICustomMessagePayload } from "../../../../interface";
-import * as mesApi from "@/api/message/index";
-import moment from "moment";
+import Icon from "../../../common/Icon.vue";
+import star from "../../../../assets/icon/star-light.png";
 interface Props {
   messageItem: IMessageModel;
   content: any;
 }
+
 const props = withDefaults(defineProps<Props>(), {
   messageItem: undefined,
   content: undefined,
 });
+const userType = ref(uni.getStorageSync("userId").includes("A"));
+console.log("1123123", userType);
+
 const custom = ref();
 const message = ref<IMessageModel>();
 const extension = ref();
 const isCustom = ref<ICustomMessagePayload>({
   businessID: "",
 });
+const copyPhone = ref();
+const PhoneChange = (index, record) => {
+  if (index == "1") {
+    //拨号
+  } else if (index == "2") {
+    //发短信
+  } else if (index == "3") {
+    var text = isCustom.value.iphone;
+    copyTo(text); //把要复制的内容传入到copyTo方法
+  }
+};
+function copyTo(content) {
+  var oInput = document.createElement("input"); //创建一个input
+  oInput.value = content; //给要复制的内容赋值input的value
+  document.body.appendChild(oInput); //body中添加input
+  oInput.select(); // 选择对象 .select()获取文本域中的内容
+  document.execCommand("Copy"); // 执行浏览器复制命令
+  oInput.style.display = "none"; // 把input隐藏
+  uni.showToast({
+    title: "复制成功",
+  });
+}
+const AcceptInvitation = () => {
+  //普通用户接受邀请
+};
 watchEffect(() => {
   custom.value = props.content;
   message.value = props.messageItem;
   const { payload } = props.messageItem;
   isCustom.value = payload.data || "";
   isCustom.value = JSONToObject(payload.data);
-
   if (payload.data === CHAT_MSG_CUSTOM_TYPE.SERVICE) {
     extension.value = JSONToObject(payload.extension);
   }
 });
-
-function titleImpl(time) {
-  if (time) {
-    let expireTime = moment(time).format("YYYY-MM-DD");
-    const now = moment();
-    if (expireTime && now.isAfter(expireTime)) {
-      return "已过期";
-    } else if (expireTime) {
-      return "通用红包";
-    }
-    return "";
-  } else {
-    return "";
-  }
-}
-function timeImpl(time) {
-  if (time) {
-    let expireTime = moment(time).format("YYYY-MM-DD");
-    const now = moment();
-    console.log("oldTime", expireTime);
-    if (expireTime && now.isAfter(expireTime)) {
-      return expireTime + "过期不可领取";
-    } else if (expireTime) {
-      return expireTime + "过期";
-    }
-    return "";
-  } else {
-    return "";
-  }
-}
-
-const emits = defineEmits(["draw"]);
-
-const lqhb = async (record) => {
-  // 检查过期时间
-  const expireTime = record.expireTime
-    ? moment(record.expireTime).format("YYYY-MM-DD")
-    : null;
-  const now = moment();
-  const isOk = expireTime && !now.isAfter(expireTime);
-  if (isOk) {
-    try {
-      console.log("开始请求接口");
-      let res = await mesApi.default.receiveLq(record.receiveId);
-      console.log("接口响应:", res);
-      if (res.code == 200) {
-        if (res.data) {
-          let text = props.messageItem.payload.data;
-          let msg = JSON.parse(text);
-          msg.businessID = "red_envelope_message_gq";
-          props.messageItem.payload.data = JSON.stringify(msg);
-          if (!message.value) return;
-          const messageModel = TUIStore.getMessageModel(message.value.ID);
-          messageModel.modifyMessage(props.messageItem.payload.data);
-          emits("handleDraw", { info: record, isOk: true });
-          // Toast({
-          //   message: TUITranslateService.t("红包领取成功,请在钱包余额查看!"),
-          //   type: TOAST_TYPE.SUCCESS,
-          // });
-        }
-      } else {
-        emits("handleDraw", { info: record, isOk: false ,message:res.msg});
-        // Toast({
-        //   message: TUITranslateService.t("红包领取失败,请联系客服反馈!"),
-        //   type: TOAST_TYPE.ERROR,
-        // });
-      }
-    } catch (error) {
-      console.error("接口请求出错:", error);
-    }
-  }
-  console.log("lqhb 函数执行结束");
-};
-
-const product = (record) => {
-  if (record.saleType == 2) {
-    webUni.webView.redirectTo({
-      url: `/subpages/home/carpoolGoods/product-details?id=` + record.id,
-    });
-  } else {
-    webUni.webView.redirectTo({
-      url: `/subpages/home/supply-hall/product-details?id=` + record.id,
-    });
-  }
+const openLink = (url: any) => {
+  window.open(url);
 };
 </script>
 <style lang="scss" scoped>
 @import "../../../../assets/styles/common";
-
-.evaluatered {
-  position: relative;
-  left: 40px;
-  font-size: 20px;
-  color: #fff;
+.phone {
+  min-width: 180px;
+  height: 65px;
+  border-radius: 10px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  padding: 2px;
 }
-
-.time {
-  letter-spacing: 0.1em;
-  position: relative;
-  top: 30px;
-  font-weight: 500;
-  font-size: 24rpx;
-  color: #ffffff;
-  text-align: left;
-  font-style: normal;
-}
-
-.name {
-  font-weight: bold;
-  font-size: 16px;
-  margin-right: 10px;
+.xian {
+  width: 408rpx;
+  height: 1rpx;
+  background: #000000;
+  opacity: 0.1;
 }
-
-.value {
-  font-size: 16px;
-  margin-right: 10px;
+.phone_footer {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  position: relative;
+  top: 8px;
+  height: 25px;
+  align-items: center;
 }
-
 a {
-  color: #679ce1;
+  color: #00b693;
 }
 
 .custom {
@@ -219,41 +170,6 @@ a {
   }
 
   .evaluate {
-    min-width: 240px;
-    width: 240px;
-
-    .left {
-      display: flex;
-      flex-direction: row;
-      align-items: center;
-      justify-content: center;
-      background: #7a7a7d29;
-      border-radius: 15px;
-      padding: 15px;
-    }
-
-    .right {
-      display: flex;
-      flex-direction: column;
-      align-items: flex-start;
-      height: 75px;
-      width: 70%;
-      justify-content: center;
-      line-height: 25px;
-      margin-left: 10px;
-
-      .label {
-        font-weight: bold;
-        font-size: 14px;
-        width: 100px;
-        height: auto;
-        word-wrap: break-word;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-      }
-    }
-
     ul {
       display: flex;
       padding: 10px 0;
@@ -271,31 +187,47 @@ a {
 
   .order {
     display: flex;
-
-    main {
-      padding-left: 5px;
-
-      p {
-        font-family: PingFangSC-Regular;
-        width: 145px;
-        line-height: 17px;
-        font-size: 14px;
-        color: #999;
-        letter-spacing: 0;
-        margin-bottom: 6px;
-        word-break: break-word;
+    min-height: 250px;
+    min-width: 215px;
+    flex-direction: column;
+    padding: 5px;
+    .header {
+      width: 100%;
+      height: 55px;
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+    }
+    .contont {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-around;
+      align-items: flex-start;
+      height: 190px;
+      .title {
+        font-family: PingFang SC;
+        font-weight: 500;
+        font-size: 26rpx;
+        color: #a1a1a1;
+        line-height: 44rpx;
       }
-
-      span {
-        font-family: PingFangSC-Regular;
-        line-height: 25px;
-        color: #ff7201;
+      .value {
+        font-family: PingFang SC;
+        font-weight: 500;
+        font-size: 30rpx;
+        color: #666666;
+        line-height: 44rpx;
       }
     }
+    .main {
+      padding-left: 5px;
+      width: 90%;
+      line-height: 25px;
+    }
 
     img {
-      width: 67px;
-      height: 67px;
+      width: 45px;
+      height: 45px;
     }
   }
 }

+ 16 - 17
TUIKit/components/TUIChat/message-list/message-elements/message-face.vue

@@ -1,39 +1,38 @@
 <template>
   <div
+    ref="skeleton"
     class="message-image"
   >
+    <!-- todo 统一组件处理-->
     <img
-      mode="aspectFit"
       class="message-image"
-      :src="url"
+      :src="data.url"
     >
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted } from '../../../../adapter-vue';
-import { CUSTOM_BIG_EMOJI_URL } from '../../emoji-config';
-
+import { watchEffect, ref, nextTick } from '../../../../adapter-vue';
 const props = defineProps({
   content: {
     type: Object,
     default: () => ({}),
   },
+  isPC: {
+    type: Boolean,
+    default: false,
+  },
 });
-
-const url = ref(props.content.url);
-
-onMounted(() => {
-  if (props.content.type === 'custom') {
-    if (!CUSTOM_BIG_EMOJI_URL) {
-      console.warn('CUSTOM_BIG_EMOJI_URL is required for custom emoji, please check your CUSTOM_BIG_EMOJI_URL.');
-    } else {
-      url.value = CUSTOM_BIG_EMOJI_URL + props.content.name;
-    }
-  }
+const data = ref();
+const skeleton: any = ref();
+watchEffect(() => {
+  data.value = props.content;
+  if (!data.value) return;
+  nextTick(() => {
+    // todo 大小显示
+  });
 });
 </script>
-
 <style lang="scss" scoped>
 @import "../../../../assets/styles/common";
 

+ 16 - 39
TUIKit/components/TUIChat/message-list/message-elements/message-file.vue

@@ -2,12 +2,9 @@
   <div
     class="file-message-montainer"
     :title="TUITranslateService.t('TUIChat.单击下载')"
-    @click="download"
+    @click="filePreview(props.content.url)"
   >
-    <Icon
-      :file="files"
-      class="file-icon"
-    />
+    <Icon :file="files" class="file-icon" />
     <div>
       <div>{{ props.content.name }}</div>
       <div>{{ props.content.size }}</div>
@@ -16,11 +13,14 @@
 </template>
 
 <script lang="ts" setup>
-import { withDefaults } from '../../../../adapter-vue';
-import { TUITranslateService, IMessageModel } from '@tencentcloud/chat-uikit-engine';
-import Icon from '../../../common/Icon.vue';
-import files from '../../../../assets/icon/files.png';
-import type { IFileMessageContent } from '../../../../interface';
+import { withDefaults } from "../../../../adapter-vue";
+import {
+  TUITranslateService,
+  IMessageModel,
+} from "@tencentcloud/chat-uikit-engine";
+import Icon from "../../../common/Icon.vue";
+import files from "../../../../assets/icon/files.png";
+import type { IFileMessageContent } from "../../../../interface";
 
 const props = withDefaults(
   defineProps<{
@@ -30,37 +30,14 @@ const props = withDefaults(
   {
     content: () => ({} as IFileMessageContent),
     messageItem: () => ({} as IMessageModel),
-  },
+  }
 );
 
-const download = () => {
-  if (props.messageItem.hasRiskContent) {
-    return;
-  }
-  const option = {
-    mode: 'cors',
-    headers: new Headers({
-      'Content-Type': 'application/x-www-form-urlencoded',
-    }),
-  } as RequestInit;
-  // 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(props.content.url, option)
-      .then(res => res.blob())
-      .then((blob) => {
-        const a = document.createElement('a');
-        const url = window.URL.createObjectURL(blob);
-        a.href = url;
-        a.download = props.content.name;
-        a.click();
-      });
-  } else {
-    const a = document.createElement('a');
-    a.href = props.content.url;
-    a.target = '_blank';
-    a.download = props.content.name;
-    a.click();
-  }
+// 预览简历
+const filePreview = (url) => {
+  webUni.webView.navigateTo({
+    url: "/subpages/my/addition/preview?url=" + url,
+  });
 };
 </script>
 <style lang="scss" scoped>

+ 3 - 0
TUIKit/components/TUIChat/message-list/message-elements/message-image.vue

@@ -65,6 +65,7 @@ const imageLoad = (event: Event) => {
   genImageStyles(event.detail);
 };
 
+// 预览
 const handleImagePreview = () => {
   if (props.messageItem?.status === 'success' || props.messageItem.progress === 1) {
     emits('previewImage');
@@ -76,6 +77,8 @@ const handleImagePreview = () => {
 .image-container {
   position: relative;
   background-color: #f4f4f4;
+
+  // 防止div被撑高
   font-size: 0;
 
   .message-image {

+ 16 - 92
TUIKit/components/TUIChat/message-list/message-elements/message-quote/index.vue

@@ -1,29 +1,20 @@
 <template>
   <div
-    v-if="hasQuoteContent"
+    v-if="quoteContent"
     :class="{
       'reference-content': true,
       'reverse': message.flow === 'out',
     }"
     @click="scrollToOriginalMessage"
   >
-    <div
-      v-if="isMessageRevoked"
-      class="revoked-text"
-    >
-      {{ TUITranslateService.t('TUIChat.引用内容已撤回') }}
-    </div>
-    <div
-      v-else
-      class="max-double-line"
-    >
-      {{ messageQuoteContent.messageSender }}: {{ transformTextWithKeysToEmojiNames(messageQuoteText) }}
+    <div class="max-double-line">
+      {{ quoteContent.messageSender }}: {{ decodeTextMessage(quoteContent.messageAbstract) }}
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, onMounted } from '../../../../../adapter-vue';
+import { computed } from '../../../../../adapter-vue';
 import {
   TUIStore,
   StoreName,
@@ -33,8 +24,8 @@ import {
 import { getBoundingClientRect, getScrollInfo } from '@tencentcloud/universal-api';
 import { isUniFrameWork } from '../../../../../utils/env';
 import { Toast, TOAST_TYPE } from '../../../../../components/common/Toast/index';
-import { ICloudCustomData, IQuoteContent, MessageQuoteTypeEnum } from './interface.ts';
-import { transformTextWithKeysToEmojiNames } from '../../../emoji-config';
+import type { ICloudCustomData, IQuoteContent } from './interface.ts';
+import { decodeTextMessage } from '../../../utils/emojiList';
 
 export interface IProps {
   message: IMessageModel;
@@ -51,89 +42,26 @@ const props = withDefaults(defineProps<IProps>(), {
 });
 
 let selfAddValue = 0;
-const messageQuoteText = ref<string>('');
-const hasQuoteContent = ref(false);
-const messageQuoteContent = ref<IQuoteContent>({} as IQuoteContent);
+let messageList: IMessageModel[] = [];
 
-const isMessageRevoked = computed<boolean>(() => {
-  try {
-    const cloudCustomData: ICloudCustomData = JSON.parse(props.message?.cloudCustomData || '{}');
-    const quotedMessageModel = TUIStore.getMessageModel(cloudCustomData.messageReply.messageID);
-    return quotedMessageModel?.isRevoked;
-  } catch (error) {
-    return true;
-  }
+TUIStore.watch(StoreName.CHAT, {
+  messageList(list: IMessageModel[]) {
+    messageList = list;
+  },
 });
 
-onMounted(() => {
+const quoteContent = computed<IQuoteContent | undefined>(() => {
   try {
     const cloudCustomData: ICloudCustomData = JSON.parse(props.message?.cloudCustomData || '{}');
-    hasQuoteContent.value = Boolean(cloudCustomData.messageReply);
-    if (hasQuoteContent.value) {
-      messageQuoteContent.value = cloudCustomData.messageReply;
-      messageQuoteText.value = performQuoteContent(messageQuoteContent.value);
-    }
+    return cloudCustomData.messageReply;
   } catch (error) {
-    hasQuoteContent.value = false;
+    return undefined;
   }
 });
 
-function performQuoteContent(params: IQuoteContent) {
-  let messageKey: string = '';
-  let quoteContent: string = '';
-  switch (params.messageType) {
-    case MessageQuoteTypeEnum.TYPE_TEXT:
-      messageKey = '[文本]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_CUSTOM:
-      messageKey = '[自定义消息]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_IMAGE:
-      messageKey = '[图片]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_SOUND:
-      messageKey = '[音频]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_VIDEO:
-      messageKey = '[视频]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_FILE:
-      messageKey = '[文件]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_LOCATION:
-      messageKey = '[地理位置]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_FACE:
-      messageKey = '[动画表情]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_GROUP_TIPS:
-      messageKey = '[群提示]';
-      break;
-    case MessageQuoteTypeEnum.TYPE_MERGER:
-      messageKey = '[聊天记录]';
-      break;
-    default:
-      messageKey = '[消息]';
-      break;
-  }
-  if (
-    [
-      MessageQuoteTypeEnum.TYPE_TEXT,
-      MessageQuoteTypeEnum.TYPE_MERGER,
-    ].includes(params.messageType)
-  ) {
-    quoteContent = params.messageAbstract;
-  }
-  return quoteContent ? quoteContent : TUITranslateService.t(`TUIChat.${messageKey}`);
-}
-
 async function scrollToOriginalMessage() {
-  if (isMessageRevoked.value) {
-    return;
-  }
-  const originMessageID = messageQuoteContent.value?.messageID;
-  const currentMessageList = TUIStore.getData(StoreName.CHAT, 'messageList');
-  const isOriginalMessageInScreen = currentMessageList.some(msg => msg.ID === originMessageID);
+  const originMessageID = quoteContent.value?.messageID;
+  const isOriginalMessageInScreen = messageList.some(msg => msg.ID === originMessageID);
   if (originMessageID && isOriginalMessageInScreen) {
     try {
       const scrollViewRect = await getBoundingClientRect('#messageScrollList', 'messageList');
@@ -184,10 +112,6 @@ async function scrollToOriginalMessage() {
     margin-left: auto;
 }
 
-.revoked-text {
-  color: #999;
-}
-
 .max-double-line {
   word-break: break-all;
   overflow: hidden;

+ 0 - 47
TUIKit/components/TUIChat/message-list/message-elements/message-quote/interface.ts

@@ -11,50 +11,3 @@ export interface IQuoteContent {
 export interface ICloudCustomData {
   messageReply: IQuoteContent;
 }
-
-export enum MessageQuoteTypeEnum {
-  /**
-   * none message
-   */
-  TYPE_NONE = 0,
-  /**
-   * text message
-   */
-  TYPE_TEXT = 1,
-  /**
-   * custom message
-   */
-  TYPE_CUSTOM = 2,
-  /**
-   * image message
-   */
-  TYPE_IMAGE = 3,
-  /**
-   * voice message
-   */
-  TYPE_SOUND = 4,
-  /**
-   * video message
-   */
-  TYPE_VIDEO = 5,
-  /**
-   * file message
-   */
-  TYPE_FILE = 6,
-  /**
-   * location message
-   */
-  TYPE_LOCATION = 7,
-  /**
-   * animation face message
-   */
-  TYPE_FACE = 8,
-  /**
-   * group tips message (save in message list)
-   */
-  TYPE_GROUP_TIPS = 9,
-  /**
-   * merge forward message
-   */
-  TYPE_MERGER = 10,
-}

+ 0 - 137
TUIKit/components/TUIChat/message-list/message-elements/message-record/index.vue

@@ -1,137 +0,0 @@
-<template>
-  <div>
-    <div
-      class="message-record-container"
-      @click="openMergeDetail"
-    >
-      <div
-        class="record-title"
-      >
-        {{ props.renderData.title }}
-      </div>
-      <div class="record-abstract-container">
-        <div
-          v-for="(item, index) in props.renderData.abstractList.slice(0, 7)"
-          :key="index"
-          class="record-abstract-item"
-        >
-          {{ transformTextWithKeysToEmojiNames(item) }}
-        </div>
-      </div>
-      <div class="record-footer">
-        {{ TUITranslateService.t('TUIChat.聊天记录') }}
-      </div>
-    </div>
-    <Overlay
-      v-if="!props.disabled && isPC"
-      :visible="isMessageListVisible"
-      @onOverlayClick="isMessageListVisible = false"
-    >
-      <SimpleMessageList
-        :isMounted="isMessageListVisible"
-        :renderData="props.renderData"
-        :messageID="props.messageItem.ID"
-        @closeOverlay="closeMergeDetail"
-      />
-    </Overlay>
-    <Drawer
-      v-else-if="!props.disabled && isH5 && !isUniFrameWork"
-      :visible="isMessageListVisible"
-      :isFullScreen="true"
-      :overlayColor="'transparent'"
-      :popDirection="'right'"
-    >
-      <SimpleMessageList
-        :isMounted="isMessageListVisible"
-        :renderData="props.renderData"
-        :messageID="props.messageItem.ID"
-        @closeOverlay="closeMergeDetail"
-      />
-    </Drawer>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref, withDefaults } from '../../../../../adapter-vue';
-import { TUITranslateService, IMessageModel } from '@tencentcloud/chat-uikit-engine';
-import Overlay from '../../../../common/Overlay/index.vue';
-import Drawer from '../../../../common/Drawer/index.vue';
-import SimpleMessageList from '../simple-message-list/index.vue';
-import { isH5, isPC, isUniFrameWork } from '../../../../../utils/env';
-import { transformTextWithKeysToEmojiNames } from '../../../emoji-config/index';
-import { IMergeMessageContent } from '../../../../../interface';
-
-interface IEmits {
-  (e: 'assignMessageIDInUniapp', messageID: string): void;
-}
-
-interface IProps {
-  // Core data for rendering message record card and message list
-  renderData: IMergeMessageContent;
-  /**
-   * The MessageRecord component has two main functions:
-   * 1. display message record cards primarily.
-   * 2. clicking on it and show the simple message list.
-   * When used as a nested component with the disabled prop
-   * it is only need renderData to render message record cards.
-   * Therefore, 'messageItem' and 'disabled' is not a required prop.
-   */
-  disabled?: boolean;
-  messageItem?: IMessageModel;
-}
-
-const emits = defineEmits<IEmits>();
-const props = withDefaults(defineProps<IProps>(), {
-  messageItem: () => ({}) as IMessageModel,
-  disabled: false,
-});
-
-const isMessageListVisible = ref(false);
-
-function openMergeDetail() {
-  if (props.disabled) {
-    return;
-  }
-  if (!isUniFrameWork) {
-    isMessageListVisible.value = true;
-  } else {
-    emits('assignMessageIDInUniapp', props.messageItem.ID);
-  }
-}
-
-function closeMergeDetail() {
-  isMessageListVisible.value = false;
-}
-</script>
-<style lang="scss" scoped>
-:not(not) {
-  display: flex;
-  flex-direction: column;
-  box-sizing: border-box;
-  min-width: 0;
-}
-
-.message-record-container {
-  padding: 10px 15px;
-  border: 1px solid #ddd;
-  border-radius: 10px;
-  cursor: pointer;
-  background-color: #fff;
-  max-width: 400px;
-  min-width: 180px;
-  overflow: hidden;
-
-  .record-abstract-container {
-    color: #bbb;
-    font-size: 12px;
-    margin: 8px 0;
-  }
-
-  .record-footer {
-    color: #888;
-    font-size: 11px;
-    padding-top: 5px;
-    border-top: 1px solid #eee;
-  }
-}
-</style>

+ 2 - 28
TUIKit/components/TUIChat/message-list/message-elements/message-text.vue

@@ -1,5 +1,5 @@
 <template>
-  <div :class="['message-text-container', isPC && 'text-select']">
+  <div>
     <span
       v-for="(item, index) in data.text"
       :key="index"
@@ -12,7 +12,6 @@
         v-else
         class="emoji"
         :src="item.src"
-        :alt="item.emojiKey"
       >
     </span>
   </div>
@@ -20,8 +19,6 @@
 
 <script lang="ts" setup>
 import { watchEffect, ref } from '../../../../adapter-vue';
-import { CUSTOM_BASIC_EMOJI_URL, CUSTOM_BASIC_EMOJI_URL_MAPPING } from '../../emoji-config';
-import { isPC } from '../../../../utils/env';
 interface IProps {
   content: Record<string, any>;
 }
@@ -31,40 +28,17 @@ const props = withDefaults(defineProps<IProps>(), {
 const data = ref();
 watchEffect(() => {
   data.value = props.content;
-  data.value.text?.forEach((item: { name: string; text?: string; src?: string; type?: string; emojiKey?: string }) => {
-    if (item.name === 'img' && item?.type === 'custom') {
-      if (!CUSTOM_BASIC_EMOJI_URL) {
-        console.warn('CUSTOM_BASIC_EMOJI_URL is required for custom emoji, please check your CUSTOM_BASIC_EMOJI_URL.');
-      } else if (!item.emojiKey || !CUSTOM_BASIC_EMOJI_URL_MAPPING[item.emojiKey]) {
-        console.warn('emojiKey is required for custom emoji, please check your emojiKey.');
-      } else {
-        item.src = CUSTOM_BASIC_EMOJI_URL + CUSTOM_BASIC_EMOJI_URL_MAPPING[item.emojiKey];
-      }
-    }
-  });
 });
 </script>
 <style lang="scss" scoped>
-.message-text-container {
-  display: inline;
-}
-
-.text-select {
-  -webkit-user-select: text;
-  -moz-user-select: text;
-  -ms-user-select: text;
-  user-select: text;
-}
-
 .emoji {
-  vertical-align: bottom;
   width: 20px;
   height: 20px;
 }
 
 .text {
   white-space: pre-wrap;
-  word-break: break-all;
+  word-break: break-word;
   font-size: 14px;
   text-size-adjust: none;
 }

+ 66 - 2
TUIKit/components/TUIChat/message-list/message-elements/message-timestamp.vue

@@ -8,7 +8,7 @@
 </template>
 <script setup lang="ts">
 import { toRefs, ref, watch } from '../../../../adapter-vue';
-import { calculateTimestamp } from '../../utils/utils';
+import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
 
 const props = defineProps({
   currTime: {
@@ -58,17 +58,81 @@ watch(
     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 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 `${TUITranslateService.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 `${TUITranslateService.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 "../../../../assets/styles/common";
 
 .message-timestamp {
-  margin: 10px auto;
+  margin: 0 auto;
   color: #999;
   font-size: 12px;
   overflow-wrap: anywhere;
   display: flex;
+  place-content: center center;
   align-items: center;
   text-align: center;
+  padding: 0 20px 30px;
 }
 </style>

+ 1 - 1
TUIKit/components/TUIChat/message-list/message-elements/message-video.vue

@@ -51,7 +51,7 @@ function handlerVideoPlay() {
     max-width: 120px;
     background-color: rgba(#000, 0.3);
     border-radius: 6px;
-    height: 200px;
+    height: 200px; // todo 优化高度动态获取
     font-size: 0;
   }
 

+ 52 - 36
TUIKit/components/TUIChat/message-list/message-elements/read-status/index.vue

@@ -3,7 +3,7 @@
     v-show="isShowReadStatus"
     :class="{
       'message-label': true,
-      'unread': isUseUnreadStyle,
+      unread: isUseUnreadStyle,
       'finger-point': isHoverFingerPointer,
     }"
     @click="openReadUserPanel"
@@ -13,28 +13,31 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, onMounted, onUnmounted } from '../../../../../adapter-vue';
+import {
+  computed,
+  ref,
+  onMounted,
+  onUnmounted,
+} from "../../../../../adapter-vue";
 import TUIChatEngine, {
   TUIStore,
   StoreName,
   IMessageModel,
   TUITranslateService,
-} from '@tencentcloud/chat-uikit-engine';
-import TUIChatConfig from '../../../config';
+} from "@tencentcloud/chat-uikit-engine";
 
 interface IProps {
   message: IMessageModel;
 }
 
 interface IEmits {
-  (e: 'openReadUserPanel'): void;
+  (e: "openReadUserPanel"): void;
 }
 
 const emits = defineEmits<IEmits>();
 const props = withDefaults(defineProps<IProps>(), {
-  message: () => ({}) as IMessageModel,
+  message: () => ({} as IMessageModel),
 });
-const ReadStatus = TUIChatConfig.getFeatureConfig('ReadStatus');
 
 enum ReadState {
   Read,
@@ -45,8 +48,10 @@ enum ReadState {
 }
 
 const TYPES = TUIChatEngine.TYPES;
-// User-level read receipt toggle has the highest priority.
-const isDisplayMessageReadReceipt = ref<boolean>(TUIStore.getData(StoreName.USER, 'displayMessageReadReceipt'));
+// 用户级别的已读回执开关 优先级最高
+const isDisplayMessageReadReceipt = ref<boolean>(
+  TUIStore.getData(StoreName.USER, "displayMessageReadReceipt")
+);
 
 onMounted(() => {
   TUIStore.watch(StoreName.USER, {
@@ -61,12 +66,11 @@ onUnmounted(() => {
 });
 
 const isShowReadStatus = computed<boolean>(() => {
-  if (!ReadStatus) {
-    return false;
-  }
   if (!isDisplayMessageReadReceipt.value) {
+    // 用户级已读回执开关关闭 不展示已读状态
     return false;
   }
+
   const {
     ID,
     type,
@@ -78,48 +82,59 @@ const isShowReadStatus = computed<boolean>(() => {
     needReadReceipt = false,
   } = props.message;
 
-  // Asynchronous message strike: Determine if there is risky content after the message has been sent
+  // 异步消息打击 消息已发出判断是否存在风险内容
   if (hasRiskContent) {
     return false;
   }
 
   const { groupProfile } = TUIStore.getConversationModel(conversationID) || {};
-  // AVCHATROOM and COMMUNITY chats do not display read status
-  if (groupProfile?.type === TYPES.GRP_AVCHATROOM || groupProfile?.type === TYPES.GRP_COMMUNITY) {
+  // 直播群以及社群不展示已读状态
+  if (
+    groupProfile?.type === TYPES.GRP_AVCHATROOM ||
+    groupProfile?.type === TYPES.GRP_COMMUNITY
+  ) {
     return false;
   }
 
   if (type === TYPES.MSG_CUSTOM) {
     const message = TUIStore.getMessageModel(ID);
-    // If it is a signaling message, do not display the read status
+    // 如果是信令消息 不展示阅读状态
     if (message?.getSignalingInfo() !== null) {
       return false;
     }
   }
 
-  // Unsuccessful message: Received messages do not display read status
-  if (flow !== 'out' || status !== 'success') {
+  // 未成功消息 接收消息 不展示阅读状态
+  if (flow !== "out" || status !== "success") {
     return false;
   }
 
-  if (conversationType === 'GROUP') {
+  if (conversationType === "GROUP") {
     return needReadReceipt;
-  } else if (conversationType === 'C2C') {
+  } else if (conversationType === "C2C") {
     return true;
   }
   return false;
 });
 
 const readState = computed<ReadState>(() => {
-  const { conversationType, needReadReceipt = false, isPeerRead = false } = props.message;
-  const { readCount = 0, unreadCount = 0, isPeerRead: isReceiptPeerRead = false } = props.message.readReceiptInfo;
-  if (conversationType === 'C2C') {
+  const {
+    conversationType,
+    needReadReceipt = false,
+    isPeerRead = false,
+  } = props.message;
+  const {
+    readCount = 0,
+    unreadCount = 0,
+    isPeerRead: isReceiptPeerRead = false,
+  } = props.message.readReceiptInfo;
+  if (conversationType === "C2C") {
     if (needReadReceipt) {
       return isReceiptPeerRead ? ReadState.Read : ReadState.Unread;
     } else {
       return isPeerRead ? ReadState.Read : ReadState.Unread;
     }
-  } else if (conversationType === 'GROUP') {
+  } else if (conversationType === "GROUP") {
     if (needReadReceipt) {
       if (readCount === 0) {
         return ReadState.Unread;
@@ -139,23 +154,23 @@ const readStatusText = computed(() => {
   const { readCount = 0 } = props.message.readReceiptInfo;
   switch (readState.value) {
     case ReadState.Read:
-      return TUITranslateService.t('TUIChat.已读');
+      return TUITranslateService.t("TUIChat.已读");
     case ReadState.Unread:
-      return TUITranslateService.t('TUIChat.未读');
+      return TUITranslateService.t("TUIChat.未读");
     case ReadState.AllRead:
-      return TUITranslateService.t('TUIChat.全部已读');
+      return TUITranslateService.t("TUIChat.全部已读");
     case ReadState.PartiallyRead:
-      return `${readCount}${TUITranslateService.t('TUIChat.人已读')}`;
+      return `${readCount}${TUITranslateService.t("TUIChat.人已读")}`;
     default:
-      return '';
+      return "";
   }
 });
 
 const isUseUnreadStyle = computed(() => {
   const { conversationType } = props.message;
-  if (conversationType === 'C2C') {
+  if (conversationType === "C2C") {
     return readState.value !== ReadState.Read;
-  } else if (conversationType === 'GROUP') {
+  } else if (conversationType === "GROUP") {
     return readState.value !== ReadState.AllRead;
   }
   return false;
@@ -163,15 +178,16 @@ const isUseUnreadStyle = computed(() => {
 
 const isHoverFingerPointer = computed<boolean>(() => {
   return (
-    props.message.needReadReceipt
-    && props.message.conversationType === 'GROUP'
-    && (readState.value === ReadState.PartiallyRead || readState.value === ReadState.Unread)
+    props.message.needReadReceipt &&
+    props.message.conversationType === "GROUP" &&
+    (readState.value === ReadState.PartiallyRead ||
+      readState.value === ReadState.Unread)
   );
 });
 
 function openReadUserPanel() {
   if (isHoverFingerPointer.value) {
-    emits('openReadUserPanel');
+    emits("openReadUserPanel");
   }
 }
 
@@ -189,7 +205,7 @@ function onDisplayMessageReadReceiptUpdate(isDisplay: boolean) {
   flex: 0 0 auto;
 
   &.unread {
-    color: #679ce1 !important;
+    color: #00b693 !important;
   }
 }
 

+ 0 - 434
TUIKit/components/TUIChat/message-list/message-elements/simple-message-list/index.vue

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

+ 0 - 102
TUIKit/components/TUIChat/message-list/message-elements/simple-message-list/message-container.vue

@@ -1,102 +0,0 @@
-<template>
-  <div class="simple-message-container">
-    <div class="simple-message-avatar">
-      <Avatar :url="props.avatar" />
-    </div>
-    <div>
-      <div class="simple-message-sender">
-        {{ props.sender }}
-      </div>
-      <div class="simple-message-body">
-        <div
-          :class="{
-            'simple-message-content': true,
-            'no-padding': isNoPadding
-          }"
-        >
-          <slot />
-        </div>
-        <div class="timestamp">
-          {{ calculateTimestamp(props.time*1000) }}
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { computed } from '../../../../../adapter-vue';
-import TUIChatEngine from '@tencentcloud/chat-uikit-engine';
-import Avatar from '../../../../common/Avatar/index.vue';
-import { calculateTimestamp } from '../../../utils/utils';
-
-interface IProps {
-  sender: string;
-  avatar: string;
-  type: string;
-  time: number;
-}
-
-const props = withDefaults(defineProps<IProps>(), {
-  sender: '',
-  avatar: '',
-});
-
-const TYPES = TUIChatEngine.TYPES;
-
-const isNoPadding = computed(() => {
-  return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO, TYPES.MSG_MERGER].includes(props.type);
-});
-</script>
-
-<style scoped lang="scss">
-:not(not){
-  display: flex;
-  flex-direction: column;
-  min-width: 0;
-  box-sizing: border-box;
-}
-
-.simple-message-container {
-  flex-direction: row;
-
-  .simple-message-avatar {
-    flex: 0 0 auto;
-    margin-right: 8px;
-  }
-
-  .simple-message-sender {
-    display: block;
-    max-width: 200px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    font-size: 11px;
-    color: #999;
-  }
-
-  .simple-message-body {
-    flex-direction: row;
-    align-items: flex-end;
-  }
-
-  .simple-message-content {
-    margin-top: 8px;
-    background-color: #dceafd;
-    border-radius: 0 10px 10px;
-    padding: 10px 12px;
-  }
-
-  .timestamp {
-    flex: 0 0 auto;
-    font-size: 12px;
-    color: #aaa;
-    margin-left: 6px;
-  }
-
-  .no-padding {
-    padding: 0;
-    background-color: transparent;
-  }
-}
-</style>

+ 86 - 66
TUIKit/components/TUIChat/message-list/message-group-application/index.vue

@@ -1,11 +1,9 @@
 <template>
   <div>
-    <div
-      v-if="groupApplicationCount > 0"
-      class="application-tips"
-    >
+    <div v-if="groupApplicationCount > 0" class="application-tips">
       <div>
-        {{ groupApplicationCount }}{{ TUITranslateService.t("TUIChat.条入群申请") }}
+        {{ groupApplicationCount
+        }}{{ TUITranslateService.t("TUIChat.条入群申请") }}
       </div>
       <div
         class="application-tips-btn"
@@ -31,28 +29,20 @@
           width: '360px',
           borderRadius: '12px 0 0 12px',
           boxShadow: '0 0 10px 0 #d0d0d0',
-        }
+        },
       }"
       @onOverlayClick="toggleGroupApplicationDrawerShow"
     >
       <div
         :class="{
-          'application-contaienr': true
+          'application-contaienr': true,
         }"
       >
         <header class="application-header">
-          <div
-            @click="toggleGroupApplicationDrawerShow"
-          >
-            <Icon
-              v-if="isPC"
-              :file="closeIcon"
-              :size="'16px'"
-            />
+          <div @click="toggleGroupApplicationDrawerShow">
+            <Icon v-if="isPC" :file="closeIcon" :size="'16px'" />
             <div v-else>
-              {{
-                TUITranslateService.t('关闭')
-              }}
+              {{ TUITranslateService.t("关闭") }}
             </div>
           </div>
         </header>
@@ -62,7 +52,7 @@
             :key="item.nick"
             :class="{
               'application-item': true,
-              'removed': item.isRemoved,
+              removed: item.isRemoved,
             }"
           >
             <Avatar
@@ -80,9 +70,7 @@
                 {{ TUITranslateService.t("TUIChat.申请加入") }}
               </div>
             </div>
-            <div
-              class="application-item-operation"
-            >
+            <div class="application-item-operation">
               <div
                 class="agree"
                 @click="handleApplication(item, 'Agree', index)"
@@ -104,20 +92,24 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, onUnmounted, watch } from '../../../../adapter-vue';
+import { ref, onMounted, onUnmounted, watch } from "../../../../adapter-vue";
 import {
   TUIStore,
   StoreName,
   TUITranslateService,
   TUIUserService,
   TUIGroupService,
-} from '@tencentcloud/chat-uikit-engine';
-import Icon from '../../../common/Icon.vue';
-import Avatar from '../../../common/Avatar/index.vue';
-import Drawer from '../../../common/Drawer/index.vue';
-import closeIcon from '../../../../assets/icon/close-dark.svg';
-import { isPC, isMobile } from '../../../../utils/env';
-import { IGroupApplication, IUserProfile, IChatResponese } from '../../../../interface';
+} from "@tencentcloud/chat-uikit-engine";
+import Icon from "../../../common/Icon.vue";
+import Avatar from "../../../common/Avatar/index.vue";
+import Drawer from "../../../common/Drawer/index.vue";
+import closeIcon from "../../../../assets/icon/close-dark.svg";
+import { isPC, isMobile } from "../../../../utils/env";
+import {
+  IGroupApplication,
+  IUserProfile,
+  IChatResponese,
+} from "../../../../interface";
 
 interface IProps {
   groupID: string;
@@ -130,7 +122,7 @@ interface ICustomGroupApplication {
 }
 
 const props = withDefaults(defineProps<IProps>(), {
-  groupID: '',
+  groupID: "",
 });
 
 const drawerDomInstanceRef = ref<InstanceType<typeof Drawer>>();
@@ -147,11 +139,14 @@ watch(isGroupApplicationDrawerShow, (newVal) => {
   }
 });
 
-watch(() => customGroupApplicationList.value.length, (newVal, oldVal) => {
-  if (oldVal > 0 && newVal === 0) {
-    isGroupApplicationDrawerShow.value = false;
+watch(
+  () => customGroupApplicationList.value.length,
+  (newVal, oldVal) => {
+    if (oldVal > 0 && newVal === 0) {
+      isGroupApplicationDrawerShow.value = false;
+    }
   }
-});
+);
 
 /**
  * Retrieves the current group application list based on the provided groupID.
@@ -159,8 +154,11 @@ watch(() => customGroupApplicationList.value.length, (newVal, oldVal) => {
  * @return {Promise<IGroupApplication[]>} The list of group applications for the current group.
  */
 async function getCurrentGroupApplicationList(): Promise<IGroupApplication[]> {
-  const result: IChatResponese<{ applicationList: IGroupApplication[] }> = await TUIGroupService.getGroupApplicationList();
-  const currentGroupApplicationList = result.data.applicationList.filter(application => application.groupID === props.groupID);
+  const result: IChatResponese<{ applicationList: IGroupApplication[] }> =
+    await TUIGroupService.getGroupApplicationList();
+  const currentGroupApplicationList = result.data.applicationList.filter(
+    (application) => application.groupID === props.groupID
+  );
   return currentGroupApplicationList;
 }
 
@@ -168,43 +166,64 @@ function toggleGroupApplicationDrawerShow() {
   isGroupApplicationDrawerShow.value = !isGroupApplicationDrawerShow.value;
 }
 
-async function generateCustomGroupApplicationList(): Promise<ICustomGroupApplication[]> {
+async function generateCustomGroupApplicationList(): Promise<
+  ICustomGroupApplication[]
+> {
   const applicationList = await getCurrentGroupApplicationList();
   if (applicationList.length === 0) {
     return [];
   }
-  const userIDList = applicationList.map(application => application.applicationType === 0 ? application.applicant : application.userID);
-  const { data: userProfileList } = await TUIUserService.getUserProfile({ userIDList }) as IChatResponese<IUserProfile[]>;
+  const userIDList = applicationList.map((application) =>
+    application.applicationType === 0
+      ? application.applicant
+      : application.userID
+  );
+  const { data: userProfileList } = (await TUIUserService.getUserProfile({
+    userIDList,
+  })) as IChatResponese<IUserProfile[]>;
   const mappingFromUserID2Profile: Record<string, IUserProfile> = {};
   userProfileList.forEach((profile: IUserProfile) => {
     mappingFromUserID2Profile[profile.userID] = profile;
   });
-  const groupApplicationList: ICustomGroupApplication[] = applicationList.map((application) => {
-    const profile = mappingFromUserID2Profile[application.applicationType === 0 ? application.applicant : application.userID];
-    return {
-      nick: profile.nick || profile.userID || 'anonymous',
-      avatar: profile.avatar || '',
-      isRemoved: false,
-      application: application,
-    };
-  });
+  const groupApplicationList: ICustomGroupApplication[] = applicationList.map(
+    (application) => {
+      const profile =
+        mappingFromUserID2Profile[
+          application.applicationType === 0
+            ? application.applicant
+            : application.userID
+        ];
+      return {
+        nick: profile.nick || profile.userID || "anonymous",
+        avatar: profile.avatar || "",
+        isRemoved: false,
+        application: application,
+      };
+    }
+  );
 
   return groupApplicationList;
 }
 
-function handleApplication(customApplication: ICustomGroupApplication, action: 'Agree' | 'Reject', index: number) {
+function handleApplication(
+  customApplication: ICustomGroupApplication,
+  action: "Agree" | "Reject",
+  index: number
+) {
   TUIGroupService.handleGroupApplication({
     handleAction: action,
     application: customApplication.application,
-  }).then(() => {
-    customGroupApplicationList.value[index].isRemoved = true;
-    setTimeout(() => {
-      customGroupApplicationList.value.splice(index, 1);
-      groupApplicationCount.value -= 1;
-    }, 150);
-  }).catch(() => {
-    // TODO: handle error
-  });
+  })
+    .then(() => {
+      customGroupApplicationList.value[index].isRemoved = true;
+      setTimeout(() => {
+        customGroupApplicationList.value.splice(index, 1);
+        groupApplicationCount.value -= 1;
+      }, 150);
+    })
+    .catch(() => {
+      // TODO: handle error
+    });
 }
 
 // --------------- mounted function ---------------
@@ -214,6 +233,7 @@ onMounted(() => {
     groupApplicationCount.value = applicationList.length;
   });
 
+  // 群未决消息列表
   TUIStore.watch(StoreName.GRP, {
     groupSystemNoticeList: onGroupSystemNoticeListUpdated,
   });
@@ -253,7 +273,7 @@ function onGroupSystemNoticeListUpdated() {
   width: 100%;
   padding: 5px 0;
   font-size: 14px;
-  background-color:  #fce4d3;
+  background-color: #fce4d3;
 
   .application-tips-btn {
     color: #006eff;
@@ -276,7 +296,7 @@ function onGroupSystemNoticeListUpdated() {
     right: 0;
     padding: 10px 20px;
     flex-direction: row-reverse;
-    color: #679ce1;
+    color: #00b693;
     font-size: 14px;
   }
 
@@ -316,15 +336,15 @@ function onGroupSystemNoticeListUpdated() {
       flex: 0 0 auto;
       font-size: 14px;
 
-      .agree{
-        color: #679ce1;
-        cursor: pointer
+      .agree {
+        color: #00b693;
+        cursor: pointer;
       }
 
-      .reject{
+      .reject {
         margin-left: 12px;
         color: #fb355d;
-        cursor: pointer
+        cursor: pointer;
       }
     }
   }

+ 25 - 68
TUIKit/components/TUIChat/message-list/message-tool/index.vue

@@ -4,10 +4,7 @@
     ref="messageToolDom"
     :class="['dialog-item', !isPC ? 'dialog-item-h5' : 'dialog-item-web']"
   >
-    <slot
-      v-if="featureConfig.EmojiReaction"
-      name="TUIEmojiPlugin"
-    />
+    <slot name="TUIEmojiPlugin" />
     <div
       class="dialog-item-list"
       :class="!isPC ? 'dialog-item-list-h5' : 'dialog-item-list-web'"
@@ -18,11 +15,11 @@
           :key="item.key"
           class="list-item"
           @click="getFunction(index)"
-          @mousedown="beforeCopy(item.key)"
         >
           <Icon
             :file="item.iconUrl"
-            :size="'15px'"
+            width="15px"
+            height="15px"
           />
           <span class="list-item-text">{{ item.text }}</span>
         </div>
@@ -48,34 +45,20 @@ import quoteIcon from '../../../../assets/icon/msg-quote.svg';
 import revokeIcon from '../../../../assets/icon/msg-revoke.svg';
 import forwardIcon from '../../../../assets/icon/msg-forward.svg';
 import translateIcon from '../../../../assets/icon/translate.svg';
-import multipleSelectIcon from '../../../../assets/icon/multiple-select.svg';
 import convertText from '../../../../assets/icon/convertText_zh.svg';
 import { enableSampleTaskStatus } from '../../../../utils/enableSampleTaskStatus';
-import { transformTextWithKeysToEmojiNames } from '../../emoji-config';
-import { isH5, isPC, isUniFrameWork } from '../../../../utils/env';
+import { copyText } from '../../utils/utils';
+import { decodeTextMessage } from '../../utils/emojiList';
+import { isPC, isUniFrameWork } from '../../../../utils/env';
 import { ITranslateInfo, IConvertInfo } from '../../../../interface';
-import TUIChatConfig from '../../config';
-
-// uni-app conditional compilation will not run the following code
-// #ifndef APP || APP-PLUS || MP || H5
-import CopyManager from '../../utils/copy';
-// #endif
 
 interface IProps {
   messageItem: IMessageModel;
-  isMultipleSelectMode: boolean;
-}
-
-interface IEmits {
-  (key: 'toggleMultipleSelectMode'): void;
 }
 
-const emits = defineEmits<IEmits>();
 const props = withDefaults(defineProps<IProps>(), {
-  isMultipleSelectMode: false,
   messageItem: () => ({}) as IMessageModel,
 });
-const featureConfig = TUIChatConfig.getFeatureConfig();
 
 const TYPES = TUIChatEngine.TYPES;
 
@@ -85,7 +68,7 @@ const actionItems = ref([
     text: TUITranslateService.t('TUIChat.打开'),
     iconUrl: copyIcon,
     renderCondition() {
-      if (!featureConfig.DownloadFile || !message.value) return false;
+      if (!message.value) return false;
       return isPC && (message.value?.type === TYPES.MSG_FILE
         || message.value.type === TYPES.MSG_VIDEO
         || message.value.type === TYPES.MSG_IMAGE);
@@ -97,7 +80,7 @@ const actionItems = ref([
     text: TUITranslateService.t('TUIChat.复制'),
     iconUrl: copyIcon,
     renderCondition() {
-      if (!featureConfig.CopyMessage || !message.value) return false;
+      if (!message.value) return false;
       return message.value.type === TYPES.MSG_TEXT;
     },
     clickEvent: copyMessage,
@@ -107,7 +90,7 @@ const actionItems = ref([
     text: TUITranslateService.t('TUIChat.撤回'),
     iconUrl: revokeIcon,
     renderCondition() {
-      if (!featureConfig.RevokeMessage || !message.value) return false;
+      if (!message.value) return false;
       return message.value.flow === 'out' && message.value.status === 'success';
     },
     clickEvent: revokeMessage,
@@ -117,7 +100,7 @@ const actionItems = ref([
     text: TUITranslateService.t('TUIChat.删除'),
     iconUrl: delIcon,
     renderCondition() {
-      if (!featureConfig.DeleteMessage || !message.value) return false;
+      if (!message.value) return false;
       return message.value.status === 'success';
     },
     clickEvent: deleteMessage,
@@ -127,7 +110,7 @@ const actionItems = ref([
     text: TUITranslateService.t('TUIChat.转发'),
     iconUrl: forwardIcon,
     renderCondition() {
-      if (!featureConfig.ForwardMessage || !message.value) return false;
+      if (!message.value) return false;
       return message.value.status === 'success';
     },
     clickEvent: forwardSingleMessage,
@@ -137,7 +120,7 @@ const actionItems = ref([
     text: TUITranslateService.t('TUIChat.引用'),
     iconUrl: quoteIcon,
     renderCondition() {
-      if (!featureConfig.QuoteMessage || !message.value) return false;
+      if (!message.value) return false;
       const _message = TUIStore.getMessageModel(message.value.ID);
       return message.value.status === 'success' && !_message.getSignalingInfo();
     },
@@ -149,7 +132,7 @@ const actionItems = ref([
     visible: false,
     iconUrl: translateIcon,
     renderCondition() {
-      if (!featureConfig.TranslateMessage || !message.value) return false;
+      if (!message.value) return false;
       return message.value.status === 'success' && message.value.type === TYPES.MSG_TEXT;
     },
     clickEvent: translateMessage,
@@ -160,21 +143,11 @@ const actionItems = ref([
     visible: false,
     iconUrl: convertText,
     renderCondition() {
-      if (!featureConfig.VoiceToText || !message.value) return false;
+      if (!message.value) return false;
       return message.value.status === 'success' && message.value.type === TYPES.MSG_AUDIO;
     },
     clickEvent: convertVoiceToText,
   },
-  {
-    key: 'multi-select',
-    text: TUITranslateService.t('TUIChat.多选'),
-    iconUrl: multipleSelectIcon,
-    renderCondition() {
-      if (!featureConfig.MultiSelection || !message.value) return false;
-      return message.value.status === 'success';
-    },
-    clickEvent: multipleSelectMessage,
-  },
 ]);
 
 const message = ref<IMessageModel>();
@@ -208,7 +181,7 @@ const isAllActionItemInvalid = computed(() => {
 });
 
 function getFunction(index: number) {
-  // Compatible with Vue2 and WeChat Mini Program syntax, dynamic binding is not allowed.
+  // 兼容 vue2 小程序的写法 不允许动态绑定
   actionItems.value[index].clickEvent();
 }
 
@@ -230,6 +203,7 @@ function openMessage() {
 
 function revokeMessage() {
   if (!message.value) return;
+  // 获取 messageModel
   const messageModel = TUIStore.getMessageModel(message.value.ID);
   messageModel
     .revokeMessage()
@@ -237,6 +211,7 @@ function revokeMessage() {
       enableSampleTaskStatus('revokeMessage');
     })
     .catch((error: any) => {
+      // 调用异常时业务侧可以通过 promise.catch 捕获异常进行错误处理
       if (error.code === 20016) {
         const message = TUITranslateService.t('TUIChat.已过撤回时限');
         Toast({
@@ -249,36 +224,22 @@ function revokeMessage() {
 
 function deleteMessage() {
   if (!message.value) return;
+  // 获取 messageModel
   const messageModel = TUIStore.getMessageModel(message.value.ID);
   messageModel.deleteMessage();
 }
 
 async function copyMessage() {
+  const text = decodeTextMessage(message.value?.payload?.text);
   if (isUniFrameWork) {
     TUIGlobal?.setClipboardData({
-      data: transformTextWithKeysToEmojiNames(message.value?.payload?.text),
+      data: text,
     });
   } else {
-    // uni-app conditional compilation will not run the following code
-    // #ifndef APP || APP-PLUS || MP || H5
-    CopyManager.copySelection(message.value?.payload?.text);
-    // #endif
+    copyText(text);
   }
 }
 
-function beforeCopy(key: string) {
-  // only pc support copy selection or copy full message text
-  // uni-app and h5 only support copy full message text
-  if (key !== 'copy' || isH5) {
-    return;
-  }
-
-  // uni-app conditional compilation will not run the following code
-  // #ifndef APP || APP-PLUS || MP || H5
-  CopyManager.saveCurrentSelection();
-  // #endif
-}
-
 function forwardSingleMessage() {
   if (!message.value) return;
   TUIStore.update(StoreName.CUSTOM, 'singleForwardMessageID', message.value.ID);
@@ -326,10 +287,6 @@ function convertVoiceToText() {
   });
 }
 
-function multipleSelectMessage() {
-  emits('toggleMultipleSelectMode');
-}
-
 function onMessageTranslationInfoUpdated(info: Map<string, ITranslateInfo[]>) {
   if (info === undefined) return;
   const translationInfoList = info.get(props.messageItem.conversationID) || [];
@@ -379,7 +336,7 @@ defineExpose({
     align-items: baseline;
     white-space: nowrap;
     flex-wrap: wrap;
-    max-width: 280px;
+    width: 280px;
 
     .list-item {
       padding: 4px 12px;
@@ -402,10 +359,10 @@ defineExpose({
   padding: 0;
 
   .dialog-item-list {
+    flex-wrap: nowrap;
     margin: 10px;
-    white-space: nowrap;
-    flex-wrap: wrap;
-    max-width: 280px;
+    justify-content: space-around;
+    width: 280px;
 
     .list-item {
       padding: 0 8px;

+ 1 - 1
TUIKit/components/TUIChat/message-list/message-tool/message-revoked.vue

@@ -46,9 +46,9 @@ const messageEdit = () => {
 
 .revoke {
   display: flex;
-  flex-direction: row;
   justify-content: center;
   color: #999;
+  width: 100%;
   font-size: 12px;
   margin-bottom: 10px;
 

+ 78 - 54
TUIKit/components/TUIChat/message-list/read-receipt-panel/index.vue

@@ -1,8 +1,5 @@
 <template>
-  <Overlay
-    :maskColor="'transparent'"
-    @onOverlayClick="closeReadReceiptPanel"
-  >
+  <Overlay :useMask="false" @onOverlayClick="closeReadReceiptPanel">
     <div
       :class="{
         'read-receipt-panel': true,
@@ -29,7 +26,7 @@
           :key="tabName"
           :class="{
             'read-status-counter': true,
-            'active': tabName === currentTabName,
+            active: tabName === currentTabName,
           }"
           @click="toggleTabName(tabName)"
         >
@@ -37,7 +34,9 @@
             {{ tabInfo[tabName].tabName }}
           </div>
           <div class="status-count">
-            {{ tabInfo[tabName].count === undefined ? "" : tabInfo[tabName].count }}
+            {{
+              tabInfo[tabName].count === undefined ? "" : tabInfo[tabName].count
+            }}
           </div>
         </div>
       </div>
@@ -46,7 +45,7 @@
           v-if="tabInfo[currentTabName].count === 0 && isFirstLoadFinished"
           class="empty-list-tip"
         >
-          - {{ TUITranslateService.t('TUIChat.空') }} -
+          - {{ TUITranslateService.t("TUIChat.空") }} -
         </div>
         <template v-else-if="isFirstLoadFinished">
           <template v-if="currentTabName === 'unread'">
@@ -82,10 +81,7 @@
             </div>
           </template>
         </template>
-        <div
-          v-if="isFirstLoadFinished"
-          class="fetch-more-container"
-        >
+        <div v-if="isFirstLoadFinished" class="fetch-more-container">
           <FetchMore
             :isFetching="isPullDownFetching"
             :isTerminateObserve="isStopFetchMore"
@@ -98,35 +94,49 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, watch, nextTick } from '../../../../adapter-vue';
-
-import { IMessageModel, TUIStore, TUIChatService, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
-import closeIcon from '../../../../assets/icon/icon-close.svg';
-import Icon from '../../../common/Icon.vue';
-import Overlay from '../../../common/Overlay/index.vue';
-import Avatar from '../../../common/Avatar/index.vue';
-import FetchMore from '../../../common/FetchMore/index.vue';
-import type { IGroupMessageReadMemberData, IMemberData, ITabInfo, TabName } from './interface';
-import { isMobile } from '../../../../utils/env';
-
-type ReadType = 'unread' | 'read' | 'all';
+import { ref, onMounted, watch, nextTick } from "../../../../adapter-vue";
+
+import {
+  IMessageModel,
+  TUIStore,
+  TUIChatService,
+  TUITranslateService,
+} from "@tencentcloud/chat-uikit-engine";
+import closeIcon from "../../../../assets/icon/icon-close.svg";
+import Icon from "../../../common/Icon.vue";
+import Overlay from "../../../common/Overlay/index.vue";
+import Avatar from "../../../common/Avatar/index.vue";
+import FetchMore from "../../../common/FetchMore/index.vue";
+import type {
+  IGroupMessageReadMemberData,
+  IMemberData,
+  ITabInfo,
+  TabName,
+} from "./interface";
+import { isMobile } from "../../../../utils/env";
+
+type ReadType = "unread" | "read" | "all";
 
 interface IProps {
   message: IMessageModel;
 }
 
 interface IEmits {
-  (key: 'setReadReceiptPanelVisible', visible: boolean, message?: IMessageModel): void;
+  (
+    key: "setReadReceiptPanelVisible",
+    visible: boolean,
+    message?: IMessageModel
+  ): void;
 }
 
 const emits = defineEmits<IEmits>();
 const props = withDefaults(defineProps<IProps>(), {
-  message: () => ({}) as IMessageModel,
+  message: () => ({} as IMessageModel),
 });
 
-let lastUnreadCursor: string = '';
-let lastReadCursor: string = '';
-const tabNameList: TabName[] = ['unread', 'read'];
+let lastUnreadCursor: string = "";
+let lastReadCursor: string = "";
+const tabNameList: TabName[] = ["unread", "read"];
 const isListFetchCompleted: Record<TabName, boolean> = {
   unread: false,
   read: false,
@@ -137,7 +147,7 @@ const isPullDownFetching = ref<boolean>(false);
 const isPanelClose = ref<boolean>(false);
 const isFirstLoadFinished = ref<boolean>(false);
 const isStopFetchMore = ref<boolean>(false);
-const currentTabName = ref<TabName>('unread');
+const currentTabName = ref<TabName>("unread");
 const tabInfo = ref<ITabInfo>(generateInitalTabInfo());
 
 onMounted(async () => {
@@ -148,19 +158,22 @@ onMounted(async () => {
 });
 
 watch(
+  // uniapp下监听不到数据变化
   () => props.message.readReceiptInfo.readCount,
   () => {
     initAndRefetchReceiptInfomation();
-  },
+  }
 );
 
-async function fetchGroupMessageRecriptMemberListByType(readType: ReadType = 'all') {
+async function fetchGroupMessageRecriptMemberListByType(
+  readType: ReadType = "all"
+) {
   const message = TUIStore.getMessageModel(props.message.ID);
 
   let unreadResult = {} as IGroupMessageReadMemberData;
   let readResult = {} as IGroupMessageReadMemberData;
 
-  if (readType === 'all' || readType === 'unread') {
+  if (readType === "all" || readType === "unread") {
     unreadResult = await TUIChatService.getGroupMessageReadMemberList({
       message,
       filter: 1,
@@ -175,7 +188,7 @@ async function fetchGroupMessageRecriptMemberListByType(readType: ReadType = 'al
     }
   }
 
-  if (readType === 'all' || readType === 'read') {
+  if (readType === "all" || readType === "read") {
     readResult = await TUIChatService.getGroupMessageReadMemberList({
       message,
       filter: 0,
@@ -191,7 +204,8 @@ async function fetchGroupMessageRecriptMemberListByType(readType: ReadType = 'al
   }
 
   // Fetch the total number of read and unread users
-  const { unreadCount: totalUnreadCount, readCount: totalReadCount } = message.readReceiptInfo;
+  const { unreadCount: totalUnreadCount, readCount: totalReadCount } =
+    message.readReceiptInfo;
 
   return {
     unreadResult: {
@@ -207,20 +221,25 @@ async function fetchGroupMessageRecriptMemberListByType(readType: ReadType = 'al
 
 async function pullDownFetchMoreData() {
   /**
-   * Use isPullDownFetching to control the state of the FetchMore component
-   * Also, implement locking for intersectionObserver under uniapp
-   * Because there is no isIntersecting in uniapp, it is impossible to determine whether the observed element has entered or exited the observation area
-  */
+   * 使用 isPullDownFetching 控制 FetchMore 组件的状态
+   * 顺便同时做 uniapp 下的 intersectionObserver 的加锁
+   * 因为 uniapp 下 没有 isIntersecting 无法判断被观察的元素进入还是退出观察区
+   */
   if (isListFetchCompleted[currentTabName.value] || isPullDownFetching.value) {
     return;
   }
   isPullDownFetching.value = true;
-  if (currentTabName.value === 'unread' || currentTabName.value === 'read') {
-    const { unreadResult, readResult } = await fetchGroupMessageRecriptMemberListByType(currentTabName.value);
+  if (currentTabName.value === "unread" || currentTabName.value === "read") {
+    const { unreadResult, readResult } =
+      await fetchGroupMessageRecriptMemberListByType(currentTabName.value);
     checkStopFetchMore();
     try {
-      tabInfo.value.unread.memberList = tabInfo.value.unread.memberList.concat(unreadResult.unreadUserInfoList || []);
-      tabInfo.value.read.memberList = tabInfo.value.read.memberList.concat(readResult.readUserInfoList || []);
+      tabInfo.value.unread.memberList = tabInfo.value.unread.memberList.concat(
+        unreadResult.unreadUserInfoList || []
+      );
+      tabInfo.value.read.memberList = tabInfo.value.read.memberList.concat(
+        readResult.readUserInfoList || []
+      );
     } finally {
       isPullDownFetching.value = false;
     }
@@ -233,16 +252,17 @@ async function pullDownFetchMoreData() {
  * @return {Promise<void>} A promise that resolves when the function has completed.
  */
 async function initAndRefetchReceiptInfomation(): Promise<void> {
-  lastUnreadCursor = '';
-  lastReadCursor = '';
+  lastUnreadCursor = "";
+  lastReadCursor = "";
   isStopFetchMore.value = false;
   isListFetchCompleted.unread = false;
   isListFetchCompleted.read = false;
-  const { unreadResult, readResult } = await fetchGroupMessageRecriptMemberListByType('all');
+  const { unreadResult, readResult } =
+    await fetchGroupMessageRecriptMemberListByType("all");
   checkStopFetchMore();
-  resetTabInfo('read', readResult.count, readResult.readUserInfoList);
-  resetTabInfo('unread', unreadResult.count, unreadResult.unreadUserInfoList);
-  resetTabInfo('close');
+  resetTabInfo("read", readResult.count, readResult.readUserInfoList);
+  resetTabInfo("unread", unreadResult.count, unreadResult.unreadUserInfoList);
+  resetTabInfo("close");
 }
 
 /**
@@ -265,7 +285,11 @@ function checkStopFetchMore(): void {
  * @param {IMemberData[]} [memberList] - The list of members to assign to the tab. Optional.
  * @return {void} - This function does not return anything.
  */
-function resetTabInfo(tabName: TabName, count?: number, memberList?: IMemberData[]): void {
+function resetTabInfo(
+  tabName: TabName,
+  count?: number,
+  memberList?: IMemberData[]
+): void {
   tabInfo.value[tabName].count = count;
   tabInfo.value[tabName].memberList = memberList || [];
 }
@@ -278,17 +302,17 @@ function resetTabInfo(tabName: TabName, count?: number, memberList?: IMemberData
 function generateInitalTabInfo(): ITabInfo {
   return {
     read: {
-      tabName: TUITranslateService.t('TUIChat.已读'),
+      tabName: TUITranslateService.t("TUIChat.已读"),
       count: undefined,
       memberList: [],
     },
     unread: {
-      tabName: TUITranslateService.t('TUIChat.未读'),
+      tabName: TUITranslateService.t("TUIChat.未读"),
       count: undefined,
       memberList: [],
     },
     close: {
-      tabName: TUITranslateService.t('TUIChat.关闭'),
+      tabName: TUITranslateService.t("TUIChat.关闭"),
       count: undefined,
       memberList: [],
     },
@@ -308,7 +332,7 @@ function toggleTabName(tabName: TabName): void {
 function closeReadReceiptPanel(): void {
   isPanelClose.value = true;
   setTimeout(() => {
-    emits('setReadReceiptPanelVisible', false);
+    emits("setReadReceiptPanelVisible", false);
   }, 200);
 }
 </script>
@@ -378,7 +402,7 @@ function closeReadReceiptPanel(): void {
       }
 
       &.active {
-        color: #679ce1;
+        color: #00b693;
       }
     }
   }

+ 58 - 32
TUIKit/components/TUIChat/message-list/scroll-button/index.vue

@@ -4,11 +4,7 @@
     class="scroll-button"
     @click="scrollToMessageListBottom"
   >
-    <Icon
-      width="10px"
-      height="10px"
-      :file="doubleArrowIcon"
-    />
+    <Icon width="10px" height="10px" :file="doubleArrowIcon" />
     <div class="scroll-button-text">
       {{ scrollButtonContent }}
     </div>
@@ -16,43 +12,53 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, onUnmounted, computed, watch } from '../../../../adapter-vue';
+import {
+  ref,
+  onMounted,
+  onUnmounted,
+  computed,
+  watch,
+} from "../../../../adapter-vue";
 import {
   TUIStore,
   StoreName,
   IMessageModel,
   IConversationModel,
   TUITranslateService,
-} from '@tencentcloud/chat-uikit-engine';
-import Icon from '../../../common/Icon.vue';
-import doubleArrowIcon from '../../../../assets/icon/double-arrow.svg';
-import { getBoundingClientRect } from '@tencentcloud/universal-api';
-import { JSONToObject } from '../../../../utils';
+} from "@tencentcloud/chat-uikit-engine";
+import Icon from "../../../common/Icon.vue";
+import doubleArrowIcon from "../../../../assets/icon/double-arrow.svg";
+import { getBoundingClientRect } from "@tencentcloud/universal-api";
+import { JSONToObject } from "../../../../utils";
 
 interface IEmits {
-  (key: 'scrollToLatestMessage'): void;
+  (key: "scrollToLatestMessage"): void;
 }
 const emits = defineEmits<IEmits>();
 
 const messageList = ref<IMessageModel[]>([]);
-const currentConversationID = ref<string>('');
+const currentConversationID = ref<string>("");
 const currentLastMessageTime = ref<number>(0);
 const newMessageCount = ref<number>(0);
 const isScrollOverOneScreen = ref<boolean>(false);
 const isExistLastMessage = ref<boolean>(false);
 const isScrollButtonVisible = ref<boolean>(false);
 const scrollButtonContent = computed(() =>
-  newMessageCount.value ? `${newMessageCount.value}${TUITranslateService.t('TUIChat.条新消息')}` : TUITranslateService.t('TUIChat.回到最新位置'),
+  newMessageCount.value
+    ? `${newMessageCount.value}${TUITranslateService.t("TUIChat.条新消息")}`
+    : TUITranslateService.t("TUIChat.回到最新位置")
 );
 
-watch(() => [isScrollOverOneScreen.value, isExistLastMessage.value],
+watch(
+  () => [isScrollOverOneScreen.value, isExistLastMessage.value],
   () => {
-    isScrollButtonVisible.value = isScrollOverOneScreen.value || isExistLastMessage.value;
+    isScrollButtonVisible.value =
+      isScrollOverOneScreen.value || isExistLastMessage.value;
     if (!isScrollButtonVisible.value) {
       resetNewMessageCount();
     }
   },
-  { immediate: true },
+  { immediate: true }
 );
 
 onMounted(() => {
@@ -76,10 +82,12 @@ onUnmounted(() => {
 });
 
 function isTypingMessage(message: IMessageModel): boolean {
-  return JSONToObject(message.payload?.data)?.businessID === 'user_typing_status';
+  return (
+    JSONToObject(message.payload?.data)?.businessID === "user_typing_status"
+  );
 }
 
-function onMessageListUpdated(newMessageList: IMessageModel[]) {
+function onMessageListUpdated(newMessageList: Array<IMessageModel>) {
   messageList.value = newMessageList || [];
   const lastMessage = messageList.value?.[messageList.value?.length - 1];
   isExistLastMessage.value = !!(
@@ -87,31 +95,48 @@ function onMessageListUpdated(newMessageList: IMessageModel[]) {
   );
 }
 
-function onNewMessageListUpdated(newMessageList: IMessageModel[]) {
+function onNewMessageListUpdated(newMessageList: Array<IMessageModel>) {
   if (Array.isArray(newMessageList) && isScrollButtonVisible.value) {
     newMessageList.forEach((message: IMessageModel) => {
-      if (message && message.conversationID === currentConversationID.value && !message.isDeleted && !message.isRevoked && !isTypingMessage(message)) {
+      if (
+        message &&
+        message.conversationID === currentConversationID.value &&
+        !message.isDeleted &&
+        !message.isRevoked &&
+        !isTypingMessage(message)
+      ) {
         newMessageCount.value += 1;
       }
     });
   }
 }
 
-function onCurrentConversationUpdated(conversation: IConversationModel | undefined) {
+function onCurrentConversationUpdated(
+  conversation: IConversationModel | undefined
+) {
   if (conversation?.conversationID !== currentConversationID.value) {
     resetNewMessageCount();
   }
-  currentConversationID.value = conversation?.conversationID || '';
+  currentConversationID.value = conversation?.conversationID || "";
   currentLastMessageTime.value = conversation?.lastMessage?.lastTime || 0;
 }
 
-// When the scroll height of the message list upwards is greater than one screen, show scrolling to the latest tips.
+// 消息列表向上的滚动高度大于一屏时,展示滚动到最新
 async function judgeScrollOverOneScreen(e: Event) {
   if (e.target) {
     try {
-      const { height } = await getBoundingClientRect(`#${(e.target as HTMLElement)?.id}`, 'messageList') || {};
-      const scrollHeight = (e.target as HTMLElement)?.scrollHeight || (e.detail as HTMLElement)?.scrollHeight;
-      const scrollTop = (e.target as HTMLElement)?.scrollTop || (e.detail as HTMLElement)?.scrollTop || 0;
+      const { height } =
+        (await getBoundingClientRect(
+          `#${(e.target as HTMLElement)?.id}`,
+          "messageList"
+        )) || {};
+      const scrollHeight =
+        (e.target as HTMLElement)?.scrollHeight ||
+        (e.detail as HTMLElement)?.scrollHeight;
+      const scrollTop =
+        (e.target as HTMLElement)?.scrollTop ||
+        (e.detail as HTMLElement)?.scrollTop ||
+        0;
       // while scroll over one screen show this scroll button.
       if (scrollHeight - scrollTop > 2 * height) {
         isScrollOverOneScreen.value = true;
@@ -124,10 +149,10 @@ async function judgeScrollOverOneScreen(e: Event) {
   }
 }
 
-// reset messageSource
+// 载入最新的 messageSource
 function resetMessageSource() {
-  if (TUIStore.getData(StoreName.CHAT, 'messageSource') !== undefined) {
-    TUIStore.update(StoreName.CHAT, 'messageSource', undefined);
+  if (TUIStore.getData(StoreName.CHAT, "messageSource") !== undefined) {
+    TUIStore.update(StoreName.CHAT, "messageSource", undefined);
   }
 }
 
@@ -136,10 +161,11 @@ function resetNewMessageCount() {
   newMessageCount.value = 0;
 }
 
+// 滚动到消息列表最底部
 function scrollToMessageListBottom() {
   resetMessageSource();
   resetNewMessageCount();
-  emits('scrollToLatestMessage');
+  emits("scrollToLatestMessage");
 }
 
 defineExpose({
@@ -169,7 +195,7 @@ defineExpose({
   &-text {
     font-family: PingFangSC-Regular, system-ui;
     font-size: 10px;
-    color: #147aff;
+    color: #00b693;
     margin-left: 3px;
   }
 }

+ 1 - 1
TUIKit/components/TUIChat/message-list/style/color.scss

@@ -1,6 +1,6 @@
 .tui-chat {
   .tui-message-list {
-    .message-more {
+    .message-more { 
       color: #999;
       cursor: pointer;
     }

+ 0 - 7
TUIKit/components/TUIChat/message-list/style/index.scss

@@ -2,10 +2,3 @@
 @import "./color";
 @import "./web";
 @import "./h5";
-
-:not(not) {
-  display: flex;
-  flex-direction: column;
-  box-sizing: border-box;
-  min-width: 0;
-}

+ 9 - 7
TUIKit/components/TUIChat/message-list/style/web.scss

@@ -14,13 +14,10 @@
     position: relative;
 
     .tui-chat-safe-tips {
-      padding: 12px 20px;
-      background-color: rgba(255, 149, 0, 0.1);
-      color: #ff8c39;
+      background-color: #fff;
+      color: #666;
       line-height: 18px;
-      font-family: PingFangSC-Regular;
       font-style: normal;
-      font-weight: 400;
       text-align: justify;
       font-size: 14px;
 
@@ -28,6 +25,9 @@
         color: #006eff;
         float: right;
       }
+      .item-item-disabled {
+        color: #979797;
+      }
     }
 
     .tui-chat-application-tips {
@@ -47,6 +47,7 @@
       flex: 1;
       height: 100%;
       overflow: hidden auto;
+      background: #f5f5f5;
 
       .message-more {
         font-size: 14px;
@@ -76,7 +77,7 @@
           font-family: PingFangSC-Regular;
           font-weight: 400;
           font-size: 10px;
-          color: #147aff;
+          color: #00b693;
           letter-spacing: 0;
           text-align: center;
           padding-left: 3px;
@@ -89,6 +90,7 @@
         }
 
         display: flex;
+        padding: 0;
         flex-direction: column;
 
         .message-item {
@@ -174,4 +176,4 @@
 ::-webkit-scrollbar-thumb {
   border-radius: 10px;
   background-color: #9a999c;
-}
+}

+ 0 - 134
TUIKit/components/TUIChat/mulitple-select-panel/index.vue

@@ -1,134 +0,0 @@
-<template>
-  <div
-    :class="{
-      'mulitple-select-panel': true,
-      'mulitple-select-panel-mobile': isMobile,
-    }"
-  >
-    <div
-      class="forward-button"
-      @click="oneByOneForwardMessage"
-    >
-      <Icon
-        :file="ForwardEachIcon"
-        :size="iconSize"
-      />
-      <span
-        :class="{
-          'forward-button-text': true,
-          'forward-button-text-mobile': isMobile,
-        }"
-      >{{ TUITranslateService.t('TUIChat.逐条转发') }}</span>
-    </div>
-    <div
-      class="forward-button"
-      @click="mergeForwardMessage"
-    >
-      <Icon
-        :file="ForwardMergeIcon"
-        :size="iconSize"
-      />
-      <span
-        :class="{
-          'forward-button-text': true,
-          'forward-button-text-mobile': isMobile,
-        }"
-      >{{ TUITranslateService.t('TUIChat.合并转发') }}</span>
-    </div>
-    <div
-      class="forward-button"
-      @click="cancelMultipleSelect"
-    >
-      <Icon
-        class="cancel-button-icon"
-        :file="AddIcon"
-        :size="iconSize"
-      />
-      <span
-        :class="{
-          'forward-button-text': true,
-          'forward-button-text-mobile': isMobile,
-        }"
-      >
-        {{ TUITranslateService.t('TUIChat.取消') }}
-      </span>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from '../../../adapter-vue';
-import {
-  TUITranslateService,
-} from '@tencentcloud/chat-uikit-engine';
-import Icon from '../../common/Icon.vue';
-import ForwardEachIcon from '../../../assets/icon/forward-each.svg';
-import ForwardMergeIcon from '../../../assets/icon/forward-merge.svg';
-import AddIcon from '../../../assets/icon/add-circle.svg';
-import { isMobile } from '../../../utils/env';
-
-interface IEmits {
-  (key: 'oneByOneForwardMessage'): void;
-  (key: 'mergeForwardMessage'): void;
-  (key: 'toggleMultipleSelectMode'): void;
-}
-
-const emits = defineEmits<IEmits>();
-
-const iconSize = ref(isMobile ? '25px' : '30px');
-
-function oneByOneForwardMessage() {
-  emits('oneByOneForwardMessage');
-}
-
-function mergeForwardMessage() {
-  emits('mergeForwardMessage');
-}
-
-function cancelMultipleSelect() {
-  emits('toggleMultipleSelectMode');
-}
-</script>
-
-<style lang="scss" scoped>
-:not(not) {
-  display: flex;
-  flex-direction: column;
-  box-sizing: border-box;
-  min-width: 0;
-}
-
-.mulitple-select-panel {
-  height: 196px;
-  border-top: 1px solid #ebebeb;
-  flex-direction: row;
-  justify-content: space-around;
-  align-items: center;
-  background-color: #EBF0F6;
-
-  &-mobile {
-    height: 64px;
-    padding-bottom: 15px;
-    flex-direction: row;
-    align-items: flex-end;
-  }
-}
-
-.forward-button {
-  justify-content: center;
-  align-items: center;
-
-  &-text {
-    margin-top: 8px;
-    font-size: 12px;
-
-    &-mobile {
-      margin-top: 2px;
-    }
-  }
-
-  .cancel-button-icon {
-    transform: rotate(45deg);
-  }
-}
-</style>

+ 0 - 17
TUIKit/components/TUIChat/offlinePushInfoManager/const.ts

@@ -1,17 +0,0 @@
-import TUIChatEngine from '@tencentcloud/chat-uikit-engine';
-export const DEFAULT_DESC: any = {
-  [TUIChatEngine.TYPES.MSG_TEXT]: '[文本]',
-  [TUIChatEngine.TYPES.MSG_FACE]: '[动画表情]',
-  [TUIChatEngine.TYPES.MSG_IMAGE]: '[图片]',
-  [TUIChatEngine.TYPES.MSG_FILE]: '[文件]',
-  [TUIChatEngine.TYPES.MSG_AUDIO]: '[语音]',
-  [TUIChatEngine.TYPES.MSG_VIDEO]: '[视频]',
-  [TUIChatEngine.TYPES.MSG_LOCATION]: '[地理位置]',
-  [TUIChatEngine.TYPES.MSG_MERGER]: '[聊天记录]',
-  [TUIChatEngine.TYPES.MSG_CUSTOM]: '[自定义消息]',
-};
-
-export enum PUSH_SCENE {
-  CHAT = 'chat',
-  CALL = 'call',
-}

+ 0 - 6
TUIKit/components/TUIChat/offlinePushInfoManager/index.ts

@@ -1,6 +0,0 @@
-import OfflinePushInfoManager from './offlinePushInfoManager';
-
-export * from './const';
-export * from './interface';
-
-export default OfflinePushInfoManager.getInstance();

+ 0 - 21
TUIKit/components/TUIChat/offlinePushInfoManager/info.ts

@@ -1,21 +0,0 @@
-import { IChatOfflinePushInfo, ICallOfflinePushInfo } from './interface';
-
-export const chatOfflinePushInfo: IChatOfflinePushInfo = {
-  androidInfo: {
-    sound: 'private_ring.mp3',
-    XiaoMiChannelID: 'high_custom_1',
-    OPPOChannelID: 'tuikit',
-  },
-  apnsInfo: {
-    sound: '01.caf',
-    image: 'https://web.sdk.qcloud.com/im/demo/latest/faviconnew.png',
-  },
-};
-
-export const callOfflinePushInfo: ICallOfflinePushInfo = {
-  title: 'call',
-  description: 'you have a call',
-  androidSound: 'private_ring',
-  androidOPPOChannelID: 'tuikit',
-  iOSSound: '01.caf',
-};

+ 0 - 49
TUIKit/components/TUIChat/offlinePushInfoManager/interface.ts

@@ -1,49 +0,0 @@
-import { IConversationModel } from '@tencentcloud/chat-uikit-engine';
-
-export interface IOfflinePushInfoCreateParams {
-  conversation: IConversationModel;
-  payload?: any;
-  messageType: string;
-}
-
-export interface IOfflinePushApnsInfo {
-  sound?: string;
-  ignoreIOSBadge?: boolean;
-  disableVoipPush?: boolean;
-  image?: string;
-}
-
-export interface IOfflinePushAndroidInfo {
-  sound?: string;
-  XiaoMiChannelID?: string;
-  OPPOChannelID?: string;
-  FCMChannelID?: string;
-  VIVOClassification?: number;
-  VIVOCategory?: string;
-  HuaWeiCategory?: string;
-  HuaWeiImage?: string;
-  HonorImage?: string;
-  GoogleImage?: string;
-}
-
-// https://web.sdk.qcloud.com/im/doc/v3/zh-cn/SDK.html#sendMessage
-export interface IChatOfflinePushInfo {
-  title?: string;
-  description?: string;
-  extension?: string;
-  androidInfo?: IOfflinePushAndroidInfo;
-  apnsInfo?: IOfflinePushApnsInfo;
-}
-
-// doc: https://cloud.tencent.com/document/product/269/105713
-export interface ICallOfflinePushInfo {
-  title?: string;
-  description?: string;
-  iOSSound?: string;
-  androidSound?: string;
-  androidOPPOChannelID?: string;
-  androidXiaoMiChannelID?: string;
-  androidFCMChannelID?: string;
-  ignoreIOSBadge?: string;
-  isDisablePush?: string;
-}

+ 0 - 76
TUIKit/components/TUIChat/offlinePushInfoManager/offlinePushInfoManager.ts

@@ -1,76 +0,0 @@
-import TUIChatEngine, { IConversationModel, StoreName, TUIStore, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
-import { transformTextWithKeysToEmojiNames } from '../emoji-config';
-import {
-  IChatOfflinePushInfo,
-  IOfflinePushInfoCreateParams,
-} from './interface';
-import { chatOfflinePushInfo, callOfflinePushInfo } from './info';
-import { DEFAULT_DESC, PUSH_SCENE } from './const';
-
-class OfflinePushInfoManager {
-  private name = 'OfflinePushInfoManager';
-  private static instance: OfflinePushInfoManager | null = null;
-
-  private offlinePushInfo: any = {};
-
-  private constructor() {
-    this.offlinePushInfo = {
-      [PUSH_SCENE.CHAT]: chatOfflinePushInfo,
-      [PUSH_SCENE.CALL]: callOfflinePushInfo,
-    };
-  }
-
-  public static getInstance(): OfflinePushInfoManager {
-    if (!OfflinePushInfoManager.instance) {
-      OfflinePushInfoManager.instance = new OfflinePushInfoManager();
-    }
-    return OfflinePushInfoManager.instance;
-  }
-
-  public getOfflinePushInfo(scene: PUSH_SCENE) {
-    if (!Object.values(PUSH_SCENE).includes(scene)) {
-      console.error(`${this.name} getOfflinePushInfo scene: ${scene} is invalid`);
-      return null;
-    }
-    return this.offlinePushInfo[scene];
-  }
-
-  private genTitle(conversation: IConversationModel, userInfo: any) {
-    let title = conversation?.getShowName();
-    if (conversation.type === TUIChatEngine.TYPES.CONV_C2C) {
-      title = userInfo?.nick || userInfo?.userID;
-    }
-    return title;
-  }
-
-  private genDesc(messageType: string, payload: any) {
-    let desc = '';
-    if (messageType === TUIChatEngine.TYPES.MSG_TEXT) {
-      desc = transformTextWithKeysToEmojiNames(payload.text);
-    }
-    if (messageType === TUIChatEngine.TYPES.MSG_CUSTOM) {
-      desc = payload.description;
-    }
-    return desc || TUITranslateService.t(`TUIChat.${DEFAULT_DESC[messageType]}`);
-  }
-
-  public create(options: IOfflinePushInfoCreateParams): IChatOfflinePushInfo {
-    const { conversation, messageType = '', payload = {} } = options || {};
-    const userInfo = TUIStore.getData(StoreName.USER, 'userProfile');
-    const entity = {
-      sender: conversation.type === TUIChatEngine.TYPES.CONV_GROUP ? conversation.groupProfile?.groupID : userInfo?.userID,
-      nickName: userInfo?.nick,
-      chatType: conversation.type === TUIChatEngine.TYPES.CONV_GROUP ? 2 : 1,
-      version: 1,
-      action: 1,
-    };
-    return {
-      title: this.genTitle(conversation, userInfo),
-      description: this.genDesc(messageType, payload),
-      extension: JSON.stringify({ entity }),
-      ...this.offlinePushInfo[PUSH_SCENE.CHAT],
-    };
-  }
-}
-
-export default OfflinePushInfoManager;

+ 7 - 7
TUIKit/components/TUIChat/server.ts

@@ -33,13 +33,13 @@ export default class TUIChatServer {
     switch (method) {
       case TUIConstants.TUIChat.SERVICE.METHOD.UPDATE_MESSAGE_LIST:
         message = params.message;
-        // Two screen-up situations
-        // 1. If the call message conversationID is currentConversation,
-        // You need to use UPDATE_MESSAGE_LIST to update the messageList of TUIStore in the engine to display it on the screen
-        // (because you cannot get the messages you sent at this time)
-        // 2. If the call message conversationID is not currentConversation,
-        // The next time you switch to the conversation where the call message is located, getMessageList can get all the call messages you sent
-        // No need to process here
+        // 两种上屏情况
+        // 1. 如果 call 消息 conversationID 为 currentConversation,
+        //    需要借助 UPDATE_MESSAGE_LIST 更新 engine 中 TUIStore 的 messageList 进行上屏
+        //    (因为此时无法获得自己发送的消息)
+        // 2. 如果 call 消息 conversationID 不是 currentConversation,
+        //    下次切换到 call 消息所在会话时, getMessageList 可以获得 所有自己发送的 call 消息
+        //    无需此处处理
         if (message?.conversationID === this.currentConversationID) {
           const messageList = [...this.currentMessageList, message];
           TUIStore.update(StoreName.CHAT, 'messageList', messageList);

+ 1 - 1
TUIKit/components/TUIChat/style/h5.scss

@@ -1,5 +1,5 @@
 .chat {
-  display: block;
+  // height: 100%;
   height: 100%;
   overflow: hidden;
 }

+ 0 - 7
TUIKit/components/TUIChat/style/index.scss

@@ -3,10 +3,3 @@
 @import './h5';
 @import './uni';
 @import './wx';
-
-:not(not) {
-  display: flex;
-  flex-direction: column;
-  box-sizing: border-box;
-  min-width: 0;
-}

+ 0 - 12
TUIKit/components/TUIChat/style/web.scss

@@ -27,18 +27,6 @@
     display: flex;
   }
 
-  &-leave-group {
-    font-size: 14px;
-    height: 160px;
-    border-top: 1px solid #efefef;
-    justify-content: center;
-    align-items: center;
-
-    &-mobile {
-      height: 50px;
-    }
-  }
-
   &-message-input {
     height: 160px;
     display: flex;

+ 0 - 70
TUIKit/components/TUIChat/utils/chatStorage.ts

@@ -1,70 +0,0 @@
-import { isUniFrameWork } from '../../../utils/env';
-import { TUIGlobal } from '@tencentcloud/universal-api';
-
-interface IChatStorage {
-  getChatStorage(key: string): any;
-  setChatStorage(key: string, value: any): void;
-}
-
-class ChatStorage implements IChatStorage {
-  private static instance: ChatStorage | null = null;
-  private static CHAT_STORAGE_KEY: string = 'TUI_CHAT_STORAGE';
-
-  private chatStorage: Record<string, any> | null = null;
-
-  private constructor() {}
-
-  public static getInstance(): ChatStorage {
-    if (!ChatStorage.instance) {
-      ChatStorage.instance = new ChatStorage();
-    }
-    return ChatStorage.instance;
-  }
-
-  public getChatStorage(key: string): any | undefined {
-    if (!this.chatStorage) {
-      this.chatStorage = this.getChatStorageFromLocalStorage();
-    }
-    if (key) {
-      return this.chatStorage[key];
-    } else {
-      throw new Error('No key provided');
-    }
-  }
-
-  public setChatStorage(key: string, value: any): void {
-    if (!this.chatStorage) {
-      this.chatStorage = this.getChatStorageFromLocalStorage();
-    }
-    this.chatStorage[key] = value;
-    try {
-      if (isUniFrameWork) {
-        TUIGlobal.setStorageSync(ChatStorage.CHAT_STORAGE_KEY, JSON.stringify(this.chatStorage));
-      } else {
-        localStorage.setItem(ChatStorage.CHAT_STORAGE_KEY, JSON.stringify(this.chatStorage));
-      }
-    } catch (error) {
-      throw new Error('Fail to set chat storage');
-    }
-  }
-
-  private getChatStorageFromLocalStorage(): Record<string, any> {
-    let chatStorageString: string = '';
-    if (isUniFrameWork) {
-      chatStorageString = TUIGlobal.getStorageSync(ChatStorage.CHAT_STORAGE_KEY) || '';
-    } else {
-      chatStorageString = localStorage.getItem(ChatStorage.CHAT_STORAGE_KEY) || '';
-    }
-    if (!chatStorageString) {
-      return {};
-    }
-    try {
-      this.chatStorage = JSON.parse(chatStorageString);
-    } catch (error) {
-      this.chatStorage = {};
-    }
-    return this.chatStorage as Record<string, any>;
-  }
-}
-
-export default ChatStorage.getInstance();

+ 0 - 86
TUIKit/components/TUIChat/utils/conversationDraft.ts

@@ -1,86 +0,0 @@
-import {
-  IMessageModel,
-  SetConversationDraftParams,
-  StoreName,
-  TUIConversationService,
-  TUIStore,
-  TUITranslateService,
-} from '@tencentcloud/chat-uikit-engine';
-import { transformTextWithKeysToEmojiNames } from '../emoji-config';
-import { JSONToObject } from '../../../utils/index';
-
-class ConversationDraftManager {
-  private static instance: ConversationDraftManager | null = null;
-  private quoteMessageMap = new Map<string, IMessageModel>();
-
-  private constructor() { }
-
-  public static getInstance(): ConversationDraftManager {
-    if (!ConversationDraftManager.instance) {
-      ConversationDraftManager.instance = new ConversationDraftManager();
-    }
-    return ConversationDraftManager.instance;
-  }
-
-  public setStore(conversationID: string, draftContent: string, abstract: string, quoteMessage?: { type: 'quote' | 'reply'; message: IMessageModel }) {
-    if (conversationID && (this.isEditorNotEmpty(draftContent) || quoteMessage?.message?.ID)) {
-      let additionalDraftInfo = {};
-      if (quoteMessage?.message?.ID) {
-        this.quoteMessageMap.set(quoteMessage.message.ID, quoteMessage.message);
-        additionalDraftInfo = { messageID: quoteMessage.message.ID, type: quoteMessage.type };
-      }
-      const draftParams: SetConversationDraftParams = {
-        conversationID: conversationID,
-        draftInfo: {
-          html: draftContent,
-          abstract: abstract,
-          ...additionalDraftInfo,
-        },
-      };
-      TUIConversationService.setConversationDraft(draftParams);
-      TUIStore.update(StoreName.CHAT, 'quoteMessage', { message: undefined, type: 'quote' });
-    }
-  }
-
-  public getStore(conversationID: string, setEditorContentCallback: (...args: any[]) => void) {
-    const conversation = TUIStore.getConversationModel(conversationID);
-    if (!conversation) {
-      return;
-    }
-    if (conversation.conversationID && conversation.draftText) {
-      const draftObject = JSONToObject(conversation.draftText);
-      TUIStore.update(StoreName.CHAT, 'quoteMessage', { message: this.quoteMessageMap.get(draftObject.messageID) || undefined, type: draftObject.type });
-      setEditorContentCallback(draftObject.html);
-    }
-    TUIConversationService.setConversationDraft({ conversationID: conversation.conversationID });
-  }
-
-  public generateAbstract(editorContent: Array<{ type: string; payload: { text?: string; file?: File } }>): string {
-    let abstract = '';
-    editorContent?.forEach((item: { type: string; payload: { text?: string; file?: File } }) => {
-      switch (item.type) {
-        case 'text':
-          abstract += transformTextWithKeysToEmojiNames(item.payload.text || '');
-          break;
-        case 'image':
-          abstract += TUITranslateService.t('TUIChat.图片');
-          break;
-        case 'video':
-          abstract += TUITranslateService.t('TUIChat.视频');
-          break;
-        case 'file':
-          abstract += TUITranslateService.t('TUIChat.文件');
-          break;
-        default:
-          break;
-      }
-    });
-    return abstract;
-  }
-
-  private isEditorNotEmpty(editorHTML: string) {
-    return editorHTML && !editorHTML.includes('is-empty') && editorHTML !== '<p></p>';
-  }
-}
-
-export default ConversationDraftManager.getInstance();

+ 215 - 0
TUIKit/components/TUIChat/utils/emoji-config/index.ts

@@ -0,0 +1,215 @@
+/**
+ * 聊天界面表情输入界面
+ * 需要注意的是, TUIKit 里面的表情包都是有版权限制的,购买的 IM 服务不包括表情包的使用权,请在上线的时候替换成自己的表情包,否则会面临法律风险
+ * 黄脸表情为腾讯云版权所有,如要使用需获得授权,请通过以下链接联系我们。
+ *
+ * Emoji input interface in the chat screen.
+ * It should be noted that the emoticons in TUIKit are copyrighted. The purchased IM service does not include the right to use the emoticons. Please replace
+ * them with your own emoticons when you go online, otherwise you will face legal risks.
+ * The yellow face emoji is copyrighted by Tencent Cloud. To use it, authorization is required. Please contact us through the following link.
+ *
+ * https://cloud.tencent.com/document/product/269/59590
+ */
+export const emojiBaseUrl = 'https://web.sdk.qcloud.com/im/assets/emoji-plugin/';
+export const emojiUrlMapping: any = {
+  '[TUIEmoji_Expect]': 'emoji_0@2x.png',
+  '[TUIEmoji_Blink]': 'emoji_1@2x.png',
+  '[TUIEmoji_Guffaw]': 'emoji_2@2x.png',
+  '[TUIEmoji_KindSmile]': 'emoji_3@2x.png',
+  '[TUIEmoji_Haha]': 'emoji_4@2x.png',
+  '[TUIEmoji_Cheerful]': 'emoji_5@2x.png',
+  '[TUIEmoji_Smile]': 'emoji_6@2x.png',
+  '[TUIEmoji_Sorrow]': 'emoji_7@2x.png',
+  '[TUIEmoji_Speechless]': 'emoji_8@2x.png',
+  '[TUIEmoji_Amazed]': 'emoji_9@2x.png',
+  '[TUIEmoji_Complacent]': 'emoji_10@2x.png',
+  '[TUIEmoji_Lustful]': 'emoji_11@2x.png',
+  '[TUIEmoji_Stareyes]': 'emoji_12@2x.png',
+  '[TUIEmoji_Giggle]': 'emoji_13@2x.png',
+  '[TUIEmoji_Daemon]': 'emoji_14@2x.png',
+  '[TUIEmoji_Rage]': 'emoji_15@2x.png',
+  '[TUIEmoji_Yawn]': 'emoji_16@2x.png',
+  '[TUIEmoji_TearsLaugh]': 'emoji_17@2x.png',
+  '[TUIEmoji_Silly]': 'emoji_18@2x.png',
+  '[TUIEmoji_Wail]': 'emoji_19@2x.png',
+  '[TUIEmoji_Kiss]': 'emoji_20@2x.png',
+  '[TUIEmoji_Trapped]': 'emoji_21@2x.png',
+  '[TUIEmoji_Fear]': 'emoji_22@2x.png',
+  '[TUIEmoji_BareTeeth]': 'emoji_23@2x.png',
+  '[TUIEmoji_FlareUp]': 'emoji_24@2x.png',
+  '[TUIEmoji_Tact]': 'emoji_25@2x.png',
+  '[TUIEmoji_Shit]': 'emoji_26@2x.png',
+  '[TUIEmoji_ShutUp]': 'emoji_27@2x.png',
+  '[TUIEmoji_Sigh]': 'emoji_28@2x.png',
+  '[TUIEmoji_Hehe]': 'emoji_29@2x.png',
+  '[TUIEmoji_Silent]': 'emoji_30@2x.png',
+  '[TUIEmoji_Skull]': 'emoji_31@2x.png',
+  '[TUIEmoji_Mask]': 'emoji_32@2x.png',
+  '[TUIEmoji_Beer]': 'emoji_33@2x.png',
+  '[TUIEmoji_Cake]': 'emoji_34@2x.png',
+  '[TUIEmoji_RedPacket]': 'emoji_35@2x.png',
+  '[TUIEmoji_Bombs]': 'emoji_36@2x.png',
+  '[TUIEmoji_Ai]': 'emoji_37@2x.png',
+  '[TUIEmoji_Celebrate]': 'emoji_38@2x.png',
+  '[TUIEmoji_Bless]': 'emoji_39@2x.png',
+  '[TUIEmoji_Flower]': 'emoji_40@2x.png',
+  '[TUIEmoji_Watermelon]': 'emoji_41@2x.png',
+  '[TUIEmoji_Cow]': 'emoji_42@2x.png',
+  '[TUIEmoji_Fool]': 'emoji_43@2x.png',
+  '[TUIEmoji_Surprised]': 'emoji_44@2x.png',
+  '[TUIEmoji_Askance]': 'emoji_45@2x.png',
+  '[TUIEmoji_Monster]': 'emoji_46@2x.png',
+  '[TUIEmoji_Pig]': 'emoji_47@2x.png',
+  '[TUIEmoji_Coffee]': 'emoji_48@2x.png',
+  '[TUIEmoji_Ok]': 'emoji_49@2x.png',
+  '[TUIEmoji_Heart]': 'emoji_50@2x.png',
+  '[TUIEmoji_Sun]': 'emoji_51@2x.png',
+  '[TUIEmoji_Moon]': 'emoji_52@2x.png',
+  '[TUIEmoji_Star]': 'emoji_53@2x.png',
+  '[TUIEmoji_Rich]': 'emoji_54@2x.png',
+  '[TUIEmoji_Fortune]': 'emoji_55@2x.png',
+  '[TUIEmoji_857]': 'emoji_56@2x.png',
+  '[TUIEmoji_666]': 'emoji_57@2x.png',
+  '[TUIEmoji_Prohibit]': 'emoji_58@2x.png',
+  '[TUIEmoji_Convinced]': 'emoji_59@2x.png',
+  '[TUIEmoji_Knife]': 'emoji_60@2x.png',
+  '[TUIEmoji_Like]': 'emoji_61@2x.png',
+};
+
+export const emojiNameMapping: any = {
+  '[TUIEmoji_Smile]': '[微笑]',
+  '[TUIEmoji_Expect]': '[期待]',
+  '[TUIEmoji_Blink]': '[眨眼]',
+  '[TUIEmoji_Guffaw]': '[大笑]',
+  '[TUIEmoji_KindSmile]': '[姨母笑]',
+  '[TUIEmoji_Haha]': '[哈哈哈]',
+  '[TUIEmoji_Cheerful]': '[愉快]',
+  '[TUIEmoji_Speechless]': '[无语]',
+  '[TUIEmoji_Amazed]': '[惊讶]',
+  '[TUIEmoji_Sorrow]': '[悲伤]',
+  '[TUIEmoji_Complacent]': '[得意]',
+  '[TUIEmoji_Silly]': '[傻了]',
+  '[TUIEmoji_Lustful]': '[色]',
+  '[TUIEmoji_Giggle]': '[憨笑]',
+  '[TUIEmoji_Kiss]': '[亲亲]',
+  '[TUIEmoji_Wail]': '[大哭]',
+  '[TUIEmoji_TearsLaugh]': '[哭笑]',
+  '[TUIEmoji_Trapped]': '[困]',
+  '[TUIEmoji_Mask]': '[口罩]',
+  '[TUIEmoji_Fear]': '[恐惧]',
+  '[TUIEmoji_BareTeeth]': '[龇牙]',
+  '[TUIEmoji_FlareUp]': '[发怒]',
+  '[TUIEmoji_Yawn]': '[打哈欠]',
+  '[TUIEmoji_Tact]': '[机智]',
+  '[TUIEmoji_Stareyes]': '[星星眼]',
+  '[TUIEmoji_ShutUp]': '[闭嘴]',
+  '[TUIEmoji_Sigh]': '[叹气]',
+  '[TUIEmoji_Hehe]': '[呵呵]',
+  '[TUIEmoji_Silent]': '[收声]',
+  '[TUIEmoji_Surprised]': '[惊喜]',
+  '[TUIEmoji_Askance]': '[白眼]',
+  '[TUIEmoji_Ok]': '[OK]',
+  '[TUIEmoji_Shit]': '[便便]',
+  '[TUIEmoji_Monster]': '[怪兽]',
+  '[TUIEmoji_Daemon]': '[恶魔]',
+  '[TUIEmoji_Rage]': '[恶魔怒]',
+  '[TUIEmoji_Fool]': '[衰]',
+  '[TUIEmoji_Pig]': '[猪]',
+  '[TUIEmoji_Cow]': '[牛]',
+  '[TUIEmoji_Ai]': '[AI]',
+  '[TUIEmoji_Skull]': '[骷髅]',
+  '[TUIEmoji_Bombs]': '[炸弹]',
+  '[TUIEmoji_Coffee]': '[咖啡]',
+  '[TUIEmoji_Cake]': '[蛋糕]',
+  '[TUIEmoji_Beer]': '[啤酒]',
+  '[TUIEmoji_Flower]': '[花]',
+  '[TUIEmoji_Watermelon]': '[瓜]',
+  '[TUIEmoji_Rich]': '[壕]',
+  '[TUIEmoji_Heart]': '[爱心]',
+  '[TUIEmoji_Moon]': '[月亮]',
+  '[TUIEmoji_Sun]': '[太阳]',
+  '[TUIEmoji_Star]': '[星星]',
+  '[TUIEmoji_RedPacket]': '[红包]',
+  '[TUIEmoji_Celebrate]': '[庆祝]',
+  '[TUIEmoji_Bless]': '[福]',
+  '[TUIEmoji_Fortune]': '[发]',
+  '[TUIEmoji_Convinced]': '[服]',
+  '[TUIEmoji_Prohibit]': '[禁]',
+  '[TUIEmoji_666]': '[666]',
+  '[TUIEmoji_857]': '[857]',
+  '[TUIEmoji_Knife]': '[刀]',
+  '[TUIEmoji_Like]': '[赞]',
+};
+
+export const emojiKeyMapping: any = {
+  '[微笑]': '[TUIEmoji_Smile]',
+  '[期待]': '[TUIEmoji_Expect]',
+  '[眨眼]': '[TUIEmoji_Blink]',
+  '[大笑]': '[TUIEmoji_Guffaw]',
+  '[姨母笑]': '[TUIEmoji_KindSmile]',
+  '[哈哈哈]': '[TUIEmoji_Haha]',
+  '[愉快]': '[TUIEmoji_Cheerful]',
+  '[无语]': '[TUIEmoji_Speechless]',
+  '[惊讶]': '[TUIEmoji_Amazed]',
+  '[悲伤]': '[TUIEmoji_Sorrow]',
+  '[得意]': '[TUIEmoji_Complacent]',
+  '[傻了]': '[TUIEmoji_Silly]',
+  '[色]': '[TUIEmoji_Lustful]',
+  '[憨笑]': '[TUIEmoji_Giggle]',
+  '[亲亲]': '[TUIEmoji_Kiss]',
+  '[大哭]': '[TUIEmoji_Wail]',
+  '[哭笑]': '[TUIEmoji_TearsLaugh]',
+  '[困]': '[TUIEmoji_Trapped]',
+  '[口罩]': '[TUIEmoji_Mask]',
+  '[恐惧]': '[TUIEmoji_Fear]',
+  '[龇牙]': '[TUIEmoji_BareTeeth]',
+  '[发怒]': '[TUIEmoji_FlareUp]',
+  '[打哈欠]': '[TUIEmoji_Yawn]',
+  '[机智]': '[TUIEmoji_Tact]',
+  '[星星眼]': '[TUIEmoji_Stareyes]',
+  '[闭嘴]': '[TUIEmoji_ShutUp]',
+  '[叹气]': '[TUIEmoji_Sigh]',
+  '[呵呵]': '[TUIEmoji_Hehe]',
+  '[收声]': '[TUIEmoji_Silent]',
+  '[惊喜]': '[TUIEmoji_Surprised]',
+  '[白眼]': '[TUIEmoji_Askance]',
+  '[OK]': '[TUIEmoji_Ok]',
+  '[便便]': '[TUIEmoji_Shit]',
+  '[怪兽]': '[TUIEmoji_Monster]',
+  '[恶魔]': '[TUIEmoji_Daemon]',
+  '[恶魔怒]': '[TUIEmoji_Rage]',
+  '[衰]': '[TUIEmoji_Fool]',
+  '[猪]': '[TUIEmoji_Pig]',
+  '[牛]': '[TUIEmoji_Cow]',
+  '[AI]': '[TUIEmoji_Ai]',
+  '[骷髅]': '[TUIEmoji_Skull]',
+  '[炸弹]': '[TUIEmoji_Bombs]',
+  '[咖啡]': '[TUIEmoji_Coffee]',
+  '[蛋糕]': '[TUIEmoji_Cake]',
+  '[啤酒]': '[TUIEmoji_Beer]',
+  '[花]': '[TUIEmoji_Flower]',
+  '[瓜]': '[TUIEmoji_Watermelon]',
+  '[壕]': '[TUIEmoji_Rich]',
+  '[爱心]': '[TUIEmoji_Heart]',
+  '[月亮]': '[TUIEmoji_Moon]',
+  '[太阳]': '[TUIEmoji_Sun]',
+  '[星星]': '[TUIEmoji_Star]',
+  '[红包]': '[TUIEmoji_RedPacket]',
+  '[庆祝]': '[TUIEmoji_Celebrate]',
+  '[福]': '[TUIEmoji_Bless]',
+  '[发]': '[TUIEmoji_Fortune]',
+  '[服]': '[TUIEmoji_Convinced]',
+  '[禁]': '[TUIEmoji_Prohibit]',
+  '[666]': '[TUIEmoji_666]',
+  '[857]': '[TUIEmoji_857]',
+  '[刀]': '[TUIEmoji_Knife]',
+  '[赞]': '[TUIEmoji_Like]',
+};
+
+const emojiConfig = {
+  emojiBaseUrl,
+  emojiUrlMapping,
+  emojiNameMapping,
+  emojiKeyMapping,
+};
+export { emojiConfig };

+ 66 - 0
TUIKit/components/TUIChat/utils/emojiList.ts

@@ -0,0 +1,66 @@
+import { IEmojiList } from '../../../interface';
+import { EMOJI_TYPE } from '../../../constant';
+import { emojiConfig } from './emoji-config';
+export const basicEmojiUrl = emojiConfig.emojiBaseUrl;
+export const basicEmojiMap = emojiConfig.emojiUrlMapping;
+export const basicEmojiList = emojiConfig.emojiNameMapping;
+export const basicEmojiKey = emojiConfig.emojiKeyMapping;
+export const bigEmojiUrl = 'https://web.sdk.qcloud.com/im/assets/face-elem/';
+
+export const bigEmojiList: Array<any> = [
+  {
+    type: EMOJI_TYPE.BIG,
+    index: 1,
+    url: bigEmojiUrl,
+    list: ['yz00', 'yz01', 'yz02', 'yz03', 'yz04', 'yz05', 'yz06', 'yz07', 'yz08',
+      'yz09', 'yz10', 'yz11', 'yz12', 'yz13', 'yz14', 'yz15', 'yz16', 'yz17'],
+  },
+  {
+    type: EMOJI_TYPE.BIG,
+    index: 2,
+    url: bigEmojiUrl,
+    list: ['ys00', 'ys01', 'ys02', 'ys03', 'ys04', 'ys05', 'ys06', 'ys07', 'ys08',
+      'ys09', 'ys10', 'ys11', 'ys12', 'ys13', 'ys14', 'ys15'],
+  },
+  {
+    type: EMOJI_TYPE.BIG,
+    index: 3,
+    url: bigEmojiUrl,
+    list: ['gcs00', 'gcs01', 'gcs02', 'gcs03', 'gcs04', 'gcs05', 'gcs06', 'gcs07',
+      'gcs08', 'gcs09', 'gcs10', 'gcs11', 'gcs12', 'gcs13', 'gcs14', 'gcs15', 'gcs16'],
+  },
+];
+
+export const emojiList: IEmojiList = [
+  {
+    type: EMOJI_TYPE.BASIC,
+    url: basicEmojiUrl,
+    list: Object.keys(basicEmojiList),
+  },
+  ...bigEmojiList,
+];
+
+export const decodeTextMessage = (text: string) => {
+  if (!text) {
+    return '';
+  }
+  const reg = /(\[.+?\])/g;
+  let txt: string = text;
+  if (reg.test(text)) {
+    txt = text.replace(reg, match => basicEmojiList[match] || match);
+  }
+  return txt;
+};
+
+// 把中文的 value [微笑] 转化为英文的 key [TUIEmoji_Smile]
+export const transformEmojiValueToKey = (text: string) => {
+  if (!text) {
+    return '';
+  }
+  const reg = /(\[.+?\])/g;
+  let txt: string = text;
+  if (reg.test(text)) {
+    txt = text.replace(reg, match => basicEmojiKey[match] || match);
+  }
+  return txt;
+};

+ 17 - 30
TUIKit/components/TUIChat/utils/sendMessage.ts

@@ -10,7 +10,6 @@ import { Toast, TOAST_TYPE } from '../../common/Toast/index';
 import { isEnabledMessageReadReceiptGlobal } from '../utils/utils';
 import { ITipTapEditorContent } from '../../../interface';
 import { enableSampleTaskStatus } from '../../../utils/enableSampleTaskStatus';
-import OfflinePushInfoManager, { IOfflinePushInfoCreateParams } from '../offlinePushInfoManager/index';
 
 export const sendMessageErrorCodeMap: Map<number, string> = new Map([
   [3123, '文本包含本地审核拦截词'],
@@ -23,33 +22,31 @@ export const sendMessageErrorCodeMap: Map<number, string> = new Map([
   [80004, '消息中图片存在敏感内容,发送失败'],
 ]);
 
-export const createOfflinePushInfo = (conversation: IConversationModel) => {
+export const createOfflinePushInfo = (currentConversation: IConversationModel) => {
   const androidInfo = {
     sound: 'private_ring.mp3',
     XiaoMiChannelID: 'high_custom_1',
-    OPPOChannelID: 'tuikit',
   };
   const apnsInfo = {
     sound: '01.caf',
-    image: 'https://web.sdk.qcloud.com/im/demo/latest/faviconnew.png',
   };
   const userInfo = TUIStore.getData(StoreName.USER, 'userProfile');
   const entity = {
-    sender: conversation.type === TUIChatEngine.TYPES.CONV_GROUP ? conversation.groupProfile?.groupID : userInfo.userID,
+    sender: currentConversation.type === TUIChatEngine.TYPES.CONV_GROUP ? currentConversation.groupProfile?.groupID : userInfo.userID,
     nickName: userInfo.nick,
-    chatType: conversation.type === TUIChatEngine.TYPES.CONV_GROUP ? 2 : 1,
+    chatType: currentConversation.type === TUIChatEngine.TYPES.CONV_GROUP ? 2 : 1,
     version: 1,
     action: 1,
   };
   return {
-    extension: JSON.stringify({ entity }),
     androidInfo,
     apnsInfo,
+    extension: JSON.stringify({ entity }),
   };
 };
 
 /**
- * This function only processes five message types: Text/TextAt/Image/Video/File
+ * 该函数仅处理 Text TextAt Image Video File 五种消息类型
  * @param messageList
  * @param currentConversation
  */
@@ -57,7 +54,7 @@ export const sendMessages = async (
   messageList: ITipTapEditorContent[],
   currentConversation: IConversationModel,
 ) => {
-  // In case of messageJumping, the sent message is automatically cleared and returns to the bottom
+  // 有 messageJumping 的情况下,发送消息自动清空,回到底部
   if (TUIStore.getData(StoreName.CHAT, 'messageSource')) {
     TUIStore.update(StoreName.CHAT, 'messageSource', undefined);
   }
@@ -71,30 +68,26 @@ export const sendMessages = async (
       };
       // handle message typing
       let textMessageContent;
-      const sendMessageOptions = {
-        offlinePushInfo: {},
-      };
-      const offlinePushInfoCreateParams: IOfflinePushInfoCreateParams = {
-        conversation: currentConversation,
-        payload: content.payload,
-        messageType: '',
+      const sendMessageOptions: any = {
+        offlinePushInfo: createOfflinePushInfo(currentConversation),
       };
       switch (content?.type) {
         case 'text':
           textMessageContent = JSON.parse(JSON.stringify(content.payload?.text));
-          // Do not send empty messages
+          // 禁止发送空消息
           if (!textMessageContent) {
             break;
           }
-          options.payload = {
-            text: textMessageContent,
-          };
-          offlinePushInfoCreateParams.messageType = TUIChatEngine.TYPES.MSG_TEXT;
-          sendMessageOptions.offlinePushInfo = OfflinePushInfoManager.create(offlinePushInfoCreateParams);
           if (content.payload?.atUserList) {
-            options.payload.atUserList = content.payload.atUserList;
+            options.payload = {
+              text: textMessageContent,
+              atUserList: content.payload.atUserList,
+            };
             await TUIChatService.sendTextAtMessage(options, sendMessageOptions);
           } else {
+            options.payload = {
+              text: textMessageContent,
+            };
             await TUIChatService.sendTextMessage(options, sendMessageOptions);
           }
           break;
@@ -102,24 +95,18 @@ export const sendMessages = async (
           options.payload = {
             file: content.payload?.file,
           };
-          offlinePushInfoCreateParams.messageType = TUIChatEngine.TYPES.MSG_IMAGE;
-          sendMessageOptions.offlinePushInfo = OfflinePushInfoManager.create(offlinePushInfoCreateParams);
           await TUIChatService.sendImageMessage(options, sendMessageOptions);
           break;
         case 'video':
           options.payload = {
             file: content.payload?.file,
           };
-          offlinePushInfoCreateParams.messageType = TUIChatEngine.TYPES.MSG_VIDEO;
-          sendMessageOptions.offlinePushInfo = OfflinePushInfoManager.create(offlinePushInfoCreateParams);
           await TUIChatService.sendVideoMessage(options, sendMessageOptions);
           break;
         case 'file':
           options.payload = {
             file: content.payload?.file,
           };
-          offlinePushInfoCreateParams.messageType = TUIChatEngine.TYPES.MSG_FILE;
-          sendMessageOptions.offlinePushInfo = OfflinePushInfoManager.create(offlinePushInfoCreateParams);
           await TUIChatService.sendFileMessage(options, sendMessageOptions);
           break;
         default:
@@ -133,7 +120,7 @@ export const sendMessages = async (
           : error?.message,
         type: TOAST_TYPE.ERROR,
       });
-      // If the message fails to be sent and the message is a reference message, clear the reference message information
+      // 如果消息发送失败,且该消息为引用消息,清除引用消息信息
       if (TUIStore.getData(StoreName.CHAT, 'quoteMessage')) {
         TUIStore.update(StoreName.CHAT, 'quoteMessage', {});
       }

+ 50 - 58
TUIKit/components/TUIChat/utils/utils.ts

@@ -1,4 +1,5 @@
 import TUIChatEngine, { TUITranslateService, TUIStore, StoreName, IMessageModel } from '@tencentcloud/chat-uikit-engine';
+import { Toast, TOAST_TYPE } from '../../common/Toast/index';
 
 export function deepCopy(data: any, hash = new WeakMap()) {
   if (typeof data !== 'object' || data === null || data === undefined) {
@@ -83,11 +84,12 @@ export const isCreateGroupCustomMessage = (message: IMessageModel) => {
 };
 
 /**
- * displayMessageReadReceipt: User-level control to display message read status
- * After turning off, the messages you send and receive will not have message read status
- * You will not be able to see whether the other party has read their messages, and they will also not be able to see whether you have read the messages they sent
+ * displayMessageReadReceipt 用户级别控制展示消息阅读状态
+ * 关闭后 你收发的消息均不带消息阅读状态
+ * 你将无法看到对方是否已读 同时对方也无法看到他发送的消息你是否已读
+ *
+ * enabledMessageReadReceipt app级别是否开启已读回执
  *
- * enabledMessageReadReceipt: App-level setting to enable read receipts
  * @return {boolean} - Returns a boolean value indicating if the message read receipt is enabled globally.
  */
 export function isEnabledMessageReadReceiptGlobal(): boolean {
@@ -99,63 +101,53 @@ export function shallowCopyMessage(message: IMessageModel) {
   return Object.assign({}, message);
 }
 
-// calculate timestamp
-export 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 diff = todayZero - target.getTime();
+export async function copyText(text: string) {
+  const textString = text.toString();
+  try {
+    // 优先采用异步 copy navigator.clipboard 方案
+    await navigator.clipboard.writeText(textString);
+  } catch (err: any) {
+    // 不支持 navigator.clipboard 时,走兼容替代方案
+    copyTextByDocumentExecCommand(textString);
+  }
+}
 
-  function formatNum(num: number): string {
-    return num < 10 ? '0' + num : num.toString();
+function copyTextByDocumentExecCommand(textString: string) {
+  const input = document.createElement('input');
+  input.id = 'copy-input';
+  input.readOnly = true; // Prevent IOS focus from triggering keyboard events
+  input.style.position = 'absolute';
+  input.style.left = '-1000px';
+  input.style.zIndex = '-1000';
+  document.body.appendChild(input);
+  input.value = textString;
+  selectText(input, 0, textString.length);
+  if (document.execCommand('copy')) {
+    document.execCommand('copy');
+  } else {
+    Toast({
+      message: TUITranslateService.t('TUIChat.此机型暂不支持复制'),
+      type: TOAST_TYPE.ERROR,
+    });
   }
+  input.blur();
+}
 
-  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 `${TUITranslateService.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 `${TUITranslateService.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())}`;
+function selectText(
+  textbox: HTMLInputElement,
+  startIndex: number,
+  stopIndex: number,
+) {
+  if ((textbox as any).createTextRange) {
+    // ie
+    const range = (textbox as any).createTextRange();
+    range.collapse(true);
+    range.moveStart('character', startIndex); // start character
+    range.moveEnd('character', stopIndex - startIndex); // end character
+    range.select();
   } 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(),
-    )}`;
+    // firefox / chrome
+    (textbox as any).setSelectionRange(startIndex, stopIndex);
+    (textbox as any).focus();
   }
 }

+ 3 - 21
TUIKit/components/TUIChat/utils/wordsList.ts

@@ -1,29 +1,11 @@
 export const wordsList = [
   {
-    value: '在吗?在吗?在吗?重要的话说三遍。',
+    value: '我可以去贵公司面试吗?',
   },
   {
-    value: '好久没聊天了,快来和我说说话~',
+    value: '对不起,我觉得该职位不太适合我,祝您早日找到合适的人选。',
   },
   {
-    value: '好的,就这么说定了。',
-  },
-  {
-    value: '感恩的心,感谢有你。',
-  },
-  {
-    value: '糟糕!是心动的感觉!',
-  },
-  {
-    value: '心疼地抱抱自己,我太难了!',
-  },
-  {
-    value: '没关系,别在意,事情过去就过去了。',
-  },
-  {
-    value: '早上好,今天也是让人期待的一天呢!',
-  },
-  {
-    value: '熬夜有什么用,又没人陪你聊天,早点休息吧。',
+    value: '我可以把简历发给您看看。',
   },
 ];

+ 4 - 4
TUIKit/components/TUIContact/contact-info/contact-info-config.ts

@@ -19,7 +19,7 @@ import {
 } from '../utils/index';
 
 export const contactMoreInfoConfig = {
-  // set friends' remark
+  // 设置好友备注
   setRemark: {
     key: 'setRemark',
     label: '备注名',
@@ -43,7 +43,7 @@ export const contactMoreInfoConfig = {
       }
     },
   },
-  // blocked list
+  // 黑名单
   blackList: {
     key: 'blackList',
     label: '加入黑名单',
@@ -65,7 +65,7 @@ export const contactMoreInfoConfig = {
       }
     },
   },
-  // Fill in verification words (applicant)
+  // 填写验证信息(申请方)
   setWords: {
     key: 'setWords',
     label: '请填写验证信息',
@@ -75,7 +75,7 @@ export const contactMoreInfoConfig = {
     editType: CONTACT_INFO_MORE_EDIT_TYPE.TEXTAREA,
     editing: true,
   },
-  // Display verification words (application recipient)
+  // 展示验证信息(申请接收方)
   displayWords: {
     key: 'displayWords',
     label: '验证信息',

+ 0 - 363
TUIKit/components/TUIContact/contact-list/index copy.vue

@@ -1,363 +0,0 @@
-<template>
-  <ul
-    v-if="!contactSearchingStatus"
-    :class="['tui-contact-list', !isPC && 'tui-contact-list-h5']"
-  >
-    <li
-      v-for="(contactListObj, key) in contactListMap"
-      :key="key"
-      class="tui-contact-list-item"
-    >
-      <header
-        class="tui-contact-list-item-header"
-        @click="toggleCurrentContactList(key)"
-      >
-        <div class="tui-contact-list-item-header-left">
-          <Icon
-            :file="currentContactListKey === key ? downSVG : rightSVG"
-            width="16px"
-            height="16px"
-          />
-          <div>{{ TUITranslateService.t(`TUIContact.${contactListObj.title}`) }}</div>
-        </div>
-        <div class="tui-contact-list-item-header-right">
-          <span
-            v-if="contactListObj.unreadCount"
-            class="tui-contact-list-item-header-right-unread"
-          >
-            {{ contactListObj.unreadCount }}
-          </span>
-        </div>
-      </header>
-      <ul :class="['tui-contact-list-item-main', currentContactListKey === key ? '' : 'hidden']">
-        <li
-          v-for="contactListItem in contactListObj.list"
-          :key="contactListItem.renderKey"
-          class="tui-contact-list-item-main-item"
-          :class="['selected']"
-          @click="selectItem(contactListItem)"
-        >
-          <ContactListItem
-            :item="contactListItem"
-            :displayOnlineStatus="displayOnlineStatus && key === 'friendList'"
-          />
-        </li>
-      </ul>
-    </li>
-  </ul>
-  <ul
-    v-else
-    class="tui-contact-list"
-  >
-    <li
-      v-for="(item, key) in contactSearchResult"
-      :key="key"
-      class="tui-contact-list-item"
-    >
-      <div
-        v-if="item.list[0]"
-        class="tui-contact-search-list"
-      >
-        <div class="tui-contact-search-list-title">
-          {{ TUITranslateService.t(`TUIContact.${item.label}`) }}
-        </div>
-        <div
-          v-for="(listItem, index) in item.list"
-          :key="index"
-          class="tui-contact-search-list-item"
-          :class="['selected']"
-          @click="selectItem(listItem)"
-        >
-          <ContactListItem
-            :item="listItem"
-            :displayOnlineStatus="false"
-          />
-        </div>
-      </div>
-    </li>
-    <div
-      v-if="isContactSearchNoResult"
-      class="tui-contact-search-list-default"
-    >
-      {{ TUITranslateService.t("TUIContact.无搜索结果") }}
-    </div>
-  </ul>
-</template>
-<script setup lang="ts">
-import {
-  TUITranslateService,
-  TUIStore,
-  StoreName,
-  IGroupModel,
-  TUIFriendService,
-  Friend,
-  FriendApplication,
-  TUIUserService,
-} from '@tencentcloud/chat-uikit-engine';
-import TUICore, { TUIConstants } from '@tencentcloud/tui-core';
-import { ref, computed, onMounted, onUnmounted, provide } from '../../../adapter-vue';
-import Icon from '../../common/Icon.vue';
-import downSVG from '../../../assets/icon/down-icon.svg';
-import rightSVG from '../../../assets/icon/right-icon.svg';
-import {
-  IContactList,
-  IContactSearchResult,
-  IBlackListUserItem,
-  IUserStatus,
-  IUserStatusMap,
-  IContactInfoType,
-} from '../../../interface';
-import ContactListItem from './contact-list-item/index.vue';
-import { isPC } from '../../../utils/env';
-
-const currentContactListKey = ref<keyof IContactList>('');
-const currentContactInfo = ref<IContactInfoType>({} as IContactInfoType);
-const contactListMap = ref<IContactList>({
-  friendApplicationList: {
-    key: 'friendApplicationList',
-    title: '新的联系人',
-    list: [] as FriendApplication[],
-    unreadCount: 0,
-  },
-  blackList: {
-    key: 'blackList',
-    title: '黑名单',
-    list: [] as IBlackListUserItem[],
-  },
-  groupList: {
-    key: 'groupList',
-    title: '我的群聊',
-    list: [] as IGroupModel[],
-  },
-  friendList: {
-    key: 'friendList',
-    title: '我的好友',
-    list: [] as Friend[],
-  },
-});
-const contactSearchingStatus = ref<boolean>(false);
-const contactSearchResult = ref<IContactSearchResult>();
-const displayOnlineStatus = ref<boolean>(false);
-const userOnlineStatusMap = ref<IUserStatusMap>();
-
-const isContactSearchNoResult = computed((): boolean => {
-  return (
-    !contactSearchResult?.value?.user?.list[0]
-    && !contactSearchResult?.value?.group?.list[0]
-  );
-});
-
-onMounted(() => {
-  TUIStore.watch(StoreName.APP, {
-    enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
-  });
-
-  TUIStore.watch(StoreName.GRP, {
-    groupList: onGroupListUpdated,
-  });
-
-  TUIStore.watch(StoreName.USER, {
-    userBlacklist: onUserBlacklistUpdated,
-    displayOnlineStatus: onDisplayOnlineStatusUpdated,
-    userStatusList: onUserStatusListUpdated,
-  });
-
-  TUIStore.watch(StoreName.FRIEND, {
-    friendList: onFriendListUpdated,
-    friendApplicationList: onFriendApplicationListUpdated,
-    friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated,
-  });
-
-  TUIStore.watch(StoreName.CUSTOM, {
-    currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
-    currentContactSearchResult: onCurrentContactSearchResultUpdated,
-    currentContactListKey: onCurrentContactListKeyUpdated,
-    currentContactInfo: onCurrentContactInfoUpdated,
-  });
-});
-
-onUnmounted(() => {
-  TUIStore.unwatch(StoreName.APP, {
-    enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
-  });
-
-  TUIStore.unwatch(StoreName.GRP, {
-    groupList: onGroupListUpdated,
-  });
-
-  TUIStore.unwatch(StoreName.USER, {
-    userBlacklist: onUserBlacklistUpdated,
-    displayOnlineStatus: onDisplayOnlineStatusUpdated,
-    userStatusList: onUserStatusListUpdated,
-  });
-
-  TUIStore.unwatch(StoreName.FRIEND, {
-    friendList: onFriendListUpdated,
-    friendApplicationList: onFriendApplicationListUpdated,
-    friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated,
-  });
-
-  TUIStore.unwatch(StoreName.CUSTOM, {
-    currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
-    currentContactSearchResult: onCurrentContactSearchResultUpdated,
-    currentContactListKey: onCurrentContactListKeyUpdated,
-    currentContactInfo: onCurrentContactInfoUpdated,
-  });
-});
-
-function toggleCurrentContactList(key: keyof IContactList) {
-  if (currentContactListKey.value === key) {
-    currentContactListKey.value = '';
-    currentContactInfo.value = {} as IContactInfoType;
-    TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
-    TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
-  } else {
-    currentContactListKey.value = key;
-    TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', key);
-    if (key === 'friendApplicationList') {
-      TUIFriendService.setFriendApplicationRead();
-    }
-  }
-}
-
-function selectItem(item: any) {
-  currentContactInfo.value = item;
-  // For a result in the search list, before viewing the contactInfo details,
-  // it is necessary to update the data for the "already in the group list/already in the friend list" situation to obtain more detailed information
-  if (contactSearchingStatus.value) {
-    let targetListItem;
-    if ((currentContactInfo.value as Friend)?.userID) {
-      targetListItem = contactListMap.value?.friendList?.list?.find(
-        (item: IContactInfoType) => (item as Friend)?.userID === (currentContactInfo.value as Friend)?.userID,
-      );
-    } else if ((currentContactInfo.value as IGroupModel)?.groupID) {
-      targetListItem = contactListMap.value?.groupList?.list?.find(
-        (item: IContactInfoType) => (item as IGroupModel)?.groupID === (currentContactInfo.value as IGroupModel)?.groupID,
-      );
-    }
-    if (targetListItem) {
-      currentContactInfo.value = targetListItem;
-    }
-  }
-  TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
-}
-
-function onDisplayOnlineStatusUpdated(status: boolean) {
-  displayOnlineStatus.value = status;
-}
-
-function onUserStatusListUpdated(list: Map<string, IUserStatus>) {
-  if (list?.size > 0) {
-    userOnlineStatusMap.value = Object.fromEntries(list?.entries());
-  }
-}
-
-function onCustomerServiceCommercialPluginUpdated(isEnabled: boolean) {
-  if (!isEnabled) {
-    return;
-  }
-
-  // After the customer purchases the customer service plug-in,
-  // the engine updates the enabledCustomerServicePlugin to true through the commercial capability bit.
-  const contactListExtensionID = TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID;
-  const tuiContactExtensionList = TUICore.getExtensionList(contactListExtensionID);
-
-  const customerData = tuiContactExtensionList.find((extension: any) => {
-    const { name, accountList = [] } = extension.data || {};
-    return name === 'customer' && accountList.length > 0;
-  });
-
-  if (customerData) {
-    const { data, text } = customerData;
-    const { accountList } = (data || {}) as { accountList: string[] };
-
-    TUIUserService.getUserProfile({ userIDList: accountList })
-      .then((res) => {
-        if (res.data.length > 0) {
-          const customerList = {
-            title: text,
-            list: res.data.map((item: any, index: number) => {
-              return {
-                ...item,
-                renderKey: generateRenderKey('customerList', item, index),
-                infoKeyList: [],
-                btnKeyList: ['enterC2CConversation'],
-              };
-            }),
-            key: 'customerList',
-          };
-          contactListMap.value = { ...contactListMap.value, customerList };
-        }
-      })
-      .catch(() => { });
-  }
-}
-
-function onGroupListUpdated(groupList: IGroupModel[]) {
-  updateContactListMap('groupList', groupList);
-}
-
-function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
-  updateContactListMap('blackList', userBlacklist);
-}
-
-function onFriendApplicationUnreadCountUpdated(friendApplicationUnreadCount: number) {
-  contactListMap.value.friendApplicationList.unreadCount = friendApplicationUnreadCount;
-}
-
-function onFriendListUpdated(friendList: Friend[]) {
-  updateContactListMap('friendList', friendList);
-}
-
-function onFriendApplicationListUpdated(friendApplicationList: FriendApplication[]) {
-  updateContactListMap('friendApplicationList', friendApplicationList);
-}
-
-function updateContactListMap(key: keyof IContactList, list: IContactInfoType[]) {
-  contactListMap.value[key].list = list;
-  contactListMap.value[key].list.map((item: IContactInfoType, index: number) => item.renderKey = generateRenderKey(key, item, index));
-  updateCurrentContactInfoFromList(contactListMap.value[key].list, key);
-}
-
-function updateCurrentContactInfoFromList(list: IContactInfoType[], type: keyof IContactList) {
-  if (
-    !(currentContactInfo.value as Friend)?.userID
-    && !(currentContactInfo.value as IGroupModel)?.groupID
-  ) {
-    return;
-  }
-  if (type === currentContactListKey.value || contactSearchingStatus.value) {
-    currentContactInfo.value = list?.find(
-      (item: any) =>
-        (item?.groupID && item?.groupID === (currentContactInfo.value as IGroupModel)?.groupID) || (item?.userID && item?.userID === (currentContactInfo.value as Friend)?.userID),
-    ) || {} as IContactInfoType;
-    TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
-  }
-}
-
-function generateRenderKey(contactListMapKey: keyof IContactList, contactInfo: IContactInfoType, index: number) {
-  return `${contactListMapKey}-${(contactInfo as Friend).userID || (contactInfo as IGroupModel).groupID || ('index' + index)}`;
-}
-
-function onCurrentContactSearchResultUpdated(searchResult: IContactSearchResult) {
-  contactSearchResult.value = searchResult;
-}
-
-function onCurrentContactSearchingStatusUpdated(searchingStatus: boolean) {
-  contactSearchingStatus.value = searchingStatus;
-  TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
-  TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
-}
-
-function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
-  currentContactInfo.value = contactInfo;
-}
-
-function onCurrentContactListKeyUpdated(contactListKey: string) {
-  currentContactListKey.value = contactListKey;
-}
-
-provide('userOnlineStatusMap', userOnlineStatusMap);
-</script>
-
-<style lang="scss" scoped src="./style/index.scss"></style>

+ 132 - 212
TUIKit/components/TUIContact/contact-list/index.vue

@@ -1,106 +1,87 @@
 <template>
-  <div>
-    <div>
-      <div class="item" @click="person">
-        <div>
-          <img :src="lxr" class="img" />
-        </div>
-        <div class="title">新的联系人</div>
-      </div>
-      <div class="item" @click="chat">
-        <div>
-          <img :src="ql" class="img" />
+  <ul
+    v-if="!contactSearchingStatus"
+    :class="['tui-contact-list', !isPC && 'tui-contact-list-h5']"
+  >
+    <li
+      v-for="(contactListObj, key) in contactListMap"
+      :key="key"
+      class="tui-contact-list-item"
+    >
+      <header
+        class="tui-contact-list-item-header"
+        @click="toggleCurrentContactList(key)"
+      >
+        <div class="tui-contact-list-item-header-left">
+          <Icon
+            :file="currentContactListKey === key ? downSVG : rightSVG"
+            width="16px"
+            height="16px"
+          />
+          <div>{{ TUITranslateService.t(`TUIContact.${contactListObj.title}`) }}</div>
         </div>
-        <div class="title">我的群聊</div>
-      </div>
-      <div class="item" @click="Blacklist">
-        <div>
-          <img :src="hmd" class="img" />
+        <div class="tui-contact-list-item-header-right">
+          <span
+            v-if="contactListObj.unreadCount"
+            class="tui-contact-list-item-header-right-unread"
+          >
+            {{ contactListObj.unreadCount }}
+          </span>
         </div>
-        <div class="title">黑名单</div>
-      </div>
-      <liu-indexed-list :dataList="dataList" @click="selectItem"></liu-indexed-list>
-    </div>
-    <liu-drag-button @clickBtn="clickBtn">
-      <Icon :file="addSVG" :width="'14px'" :height="'14px'" />
-    </liu-drag-button>
-    <!-- <ul
-      v-if="!contactSearchingStatus"
-      :class="['tui-contact-list', !isPC && 'tui-contact-list-h5']"
+      </header>
+      <ul :class="['tui-contact-list-item-main', currentContactListKey === key ? '' : 'hidden']">
+        <li
+          v-for="contactListItem in contactListObj.list"
+          :key="contactListItem.renderKey"
+          class="tui-contact-list-item-main-item"
+          :class="['selected']"
+          @click="selectItem(contactListItem)"
+        >
+          <ContactListItem
+            :item="contactListItem"
+            :displayOnlineStatus="displayOnlineStatus && key === 'friendList'"
+          />
+        </li>
+      </ul>
+    </li>
+  </ul>
+  <ul
+    v-else
+    class="tui-contact-list"
+  >
+    <li
+      v-for="(item, key) in contactSearchResult"
+      :key="key"
+      class="tui-contact-list-item"
     >
-      <li
-        v-for="(contactListObj, key) in contactListMap"
-        :key="key"
-        class="tui-contact-list-item"
+      <div
+        v-if="item.list[0]"
+        class="tui-contact-search-list"
       >
-        <header
-          class="tui-contact-list-item-header"
-          @click="toggleCurrentContactList(key)"
-        >
-          <div class="tui-contact-list-item-header-left">
-            <Icon
-              :file="currentContactListKey === key ? downSVG : rightSVG"
-              width="16px"
-              height="16px"
-            />
-            <div>{{ TUITranslateService.t(`TUIContact.${contactListObj.title}`) }}</div>
-          </div>
-          <div class="tui-contact-list-item-header-right">
-            <span
-              v-if="contactListObj.unreadCount"
-              class="tui-contact-list-item-header-right-unread"
-            >
-              {{ contactListObj.unreadCount }}
-            </span>
-          </div>
-        </header>
-        <ul
-          :class="[
-            'tui-contact-list-item-main',
-            currentContactListKey === key ? '' : 'hidden',
-          ]"
+        <div class="tui-contact-search-list-title">
+          {{ TUITranslateService.t(`TUIContact.${item.label}`) }}
+        </div>
+        <div
+          v-for="(listItem, index) in item.list"
+          :key="index"
+          class="tui-contact-search-list-item"
+          :class="['selected']"
+          @click="selectItem(listItem)"
         >
-          <li
-            v-for="contactListItem in contactListObj.list"
-            :key="contactListItem.renderKey"
-            class="tui-contact-list-item-main-item"
-            :class="['selected']"
-            @click="selectItem(contactListItem)"
-          >
-            <ContactListItem
-              :item="contactListItem"
-              :displayOnlineStatus="displayOnlineStatus && key === 'friendList'"
-            />
-          </li>
-        </ul>
-      </li>
-    </ul>
-    <ul v-else class="tui-contact-list">
-      <li
-        v-for="(item, key) in contactSearchResult"
-        :key="key"
-        class="tui-contact-list-item"
-      >
-        <div v-if="item.list[0]" class="tui-contact-search-list">
-          <div class="tui-contact-search-list-title">
-            {{ TUITranslateService.t(`TUIContact.${item.label}`) }}
-          </div>
-          <div
-            v-for="(listItem, index) in item.list"
-            :key="index"
-            class="tui-contact-search-list-item"
-            :class="['selected']"
-            @click="selectItem(listItem)"
-          >
-            <ContactListItem :item="listItem" :displayOnlineStatus="false" />
-          </div>
+          <ContactListItem
+            :item="listItem"
+            :displayOnlineStatus="false"
+          />
         </div>
-      </li>
-      <div v-if="isContactSearchNoResult" class="tui-contact-search-list-default">
-        {{ TUITranslateService.t("TUIContact.无搜索结果") }}
       </div>
-    </ul> -->
-  </div>
+    </li>
+    <div
+      v-if="isContactSearchNoResult"
+      class="tui-contact-search-list-default"
+    >
+      {{ TUITranslateService.t("TUIContact.无搜索结果") }}
+    </div>
+  </ul>
 </template>
 <script setup lang="ts">
 import {
@@ -112,17 +93,12 @@ import {
   Friend,
   FriendApplication,
   TUIUserService,
-} from "@tencentcloud/chat-uikit-engine";
-import TUICore, { TUIConstants } from "@tencentcloud/tui-core";
-import { ref, computed, onMounted, onUnmounted, provide } from "../../../adapter-vue";
-import Icon from "../../common/Icon.vue";
-import downSVG from "../../../assets/icon/down-icon.svg";
-import rightSVG from "../../../assets/icon/right-icon.svg";
-import addSVG from "../../../assets/icon/add.svg";
-import lxr from "../../../../static/img/lxr.png";
-import ql from "../../../../static/img/ql.png";
-import hmd from "../../../../static/img/hmd.png";
-import { TUIGlobal } from "@tencentcloud/universal-api";
+} from '@tencentcloud/chat-uikit-engine';
+import TUICore, { TUIConstants } from '@tencentcloud/tui-core';
+import { ref, computed, onMounted, onUnmounted, provide } from '../../../adapter-vue';
+import Icon from '../../common/Icon.vue';
+import downSVG from '../../../assets/icon/down-icon.svg';
+import rightSVG from '../../../assets/icon/right-icon.svg';
 import {
   IContactList,
   IContactSearchResult,
@@ -130,33 +106,32 @@ import {
   IUserStatus,
   IUserStatusMap,
   IContactInfoType,
-} from "../../../interface";
-import ContactListItem from "./contact-list-item/index.vue";
-import { isPC } from "../../../utils/env";
-const currentContactListKey = ref<keyof IContactList>("");
-const currentContactInfo = ref<IContactInfoType>({} as IContactInfoType);
-const dataList = ref([]);
+} from '../../../interface';
+import ContactListItem from './contact-list-item/index.vue';
+import { isPC } from '../../../utils/env';
 
+const currentContactListKey = ref<keyof IContactList>('');
+const currentContactInfo = ref<IContactInfoType>({} as IContactInfoType);
 const contactListMap = ref<IContactList>({
   friendApplicationList: {
-    key: "friendApplicationList",
-    title: "新的联系人",
+    key: 'friendApplicationList',
+    title: '新的联系人',
     list: [] as FriendApplication[],
     unreadCount: 0,
   },
   blackList: {
-    key: "blackList",
-    title: "黑名单",
+    key: 'blackList',
+    title: '黑名单',
     list: [] as IBlackListUserItem[],
   },
   groupList: {
-    key: "groupList",
-    title: "我的群聊",
+    key: 'groupList',
+    title: '我的群聊',
     list: [] as IGroupModel[],
   },
   friendList: {
-    key: "friendList",
-    title: "我的好友",
+    key: 'friendList',
+    title: '我的好友',
     list: [] as Friend[],
   },
 });
@@ -165,40 +140,14 @@ const contactSearchResult = ref<IContactSearchResult>();
 const displayOnlineStatus = ref<boolean>(false);
 const userOnlineStatusMap = ref<IUserStatusMap>();
 
-const show = ref(false);
-
-const clickBtn = () => {
-  // show.value = true;
-  TUIGlobal?.navigateTo({
-    url: "/pages/group/add",
-  });
-};
 const isContactSearchNoResult = computed((): boolean => {
   return (
-    !contactSearchResult?.value?.user?.list[0] &&
-    !contactSearchResult?.value?.group?.list[0]
+    !contactSearchResult?.value?.user?.list[0]
+    && !contactSearchResult?.value?.group?.list[0]
   );
 });
 
-const Blacklist = () => {
-  TUIGlobal?.navigateTo({
-    url: "/pages/group/black-list?list=" + JSON.stringify(contactListMap.value),
-  });
-};
-const person = () => {
-  TUIGlobal?.navigateTo({
-    url: "/pages/group/person?list=" + JSON.stringify(contactListMap.value),
-  });
-};
-const chat = () => {
-  TUIGlobal?.navigateTo({
-    url: "/pages/group/chat?list=" + JSON.stringify(contactListMap.value),
-  });
-};
-
 onMounted(() => {
-  console.log('触发----------------------');
-  
   TUIStore.watch(StoreName.APP, {
     enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
   });
@@ -225,15 +174,6 @@ onMounted(() => {
     currentContactListKey: onCurrentContactListKeyUpdated,
     currentContactInfo: onCurrentContactInfoUpdated,
   });
-
-  console.log(contactListMap.value);
-  let list = [];
-  contactListMap.value.friendList.list.forEach((e) => {
-    list.push(e.profile);
-  });
-  dataList.value = list;
-
-  console.log("dataList.value", contactListMap.value);
 });
 
 onUnmounted(() => {
@@ -267,14 +207,14 @@ onUnmounted(() => {
 
 function toggleCurrentContactList(key: keyof IContactList) {
   if (currentContactListKey.value === key) {
-    currentContactListKey.value = "";
+    currentContactListKey.value = '';
     currentContactInfo.value = {} as IContactInfoType;
-    TUIStore.update(StoreName.CUSTOM, "currentContactListKey", "");
-    TUIStore.update(StoreName.CUSTOM, "currentContactInfo", {} as IContactInfoType);
+    TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
+    TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
   } else {
     currentContactListKey.value = key;
-    TUIStore.update(StoreName.CUSTOM, "currentContactListKey", key);
-    if (key === "friendApplicationList") {
+    TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', key);
+    if (key === 'friendApplicationList') {
       TUIFriendService.setFriendApplicationRead();
     }
   }
@@ -282,27 +222,25 @@ function toggleCurrentContactList(key: keyof IContactList) {
 
 function selectItem(item: any) {
   currentContactInfo.value = item;
-  // For a result in the search list, before viewing the contactInfo details,
-  // it is necessary to update the data for the "already in the group list/already in the friend list" situation to obtain more detailed information
+  // 单独处理:
+  // 对于 搜索列表 中 某结果,查看 contactInfo 详情前,需要对于“已在群组列表/已在好友列表” 的情况进行数据更新,已获得更详细的信息
   if (contactSearchingStatus.value) {
     let targetListItem;
     if ((currentContactInfo.value as Friend)?.userID) {
       targetListItem = contactListMap.value?.friendList?.list?.find(
-        (item: IContactInfoType) =>
-          (item as Friend)?.userID === (currentContactInfo.value as Friend)?.userID
+        (item: IContactInfoType) => (item as Friend)?.userID === (currentContactInfo.value as Friend)?.userID,
       );
     } else if ((currentContactInfo.value as IGroupModel)?.groupID) {
       targetListItem = contactListMap.value?.groupList?.list?.find(
-        (item: IContactInfoType) =>
-          (item as IGroupModel)?.groupID ===
-          (currentContactInfo.value as IGroupModel)?.groupID
+        (item: IContactInfoType) => (item as IGroupModel)?.groupID === (currentContactInfo.value as IGroupModel)?.groupID,
       );
     }
     if (targetListItem) {
       currentContactInfo.value = targetListItem;
     }
   }
-  TUIStore.update(StoreName.CUSTOM, "currentContactInfo", currentContactInfo.value);
+  // 更新数据
+  TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
 }
 
 function onDisplayOnlineStatusUpdated(status: boolean) {
@@ -320,14 +258,13 @@ function onCustomerServiceCommercialPluginUpdated(isEnabled: boolean) {
     return;
   }
 
-  // After the customer purchases the customer service plug-in,
-  // the engine updates the enabledCustomerServicePlugin to true through the commercial capability bit.
+  /// 客户购买客服插件后 engine 通过商业化能力位更新将 enabledCustomerServicePlugin 设置为 true
   const contactListExtensionID = TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID;
   const tuiContactExtensionList = TUICore.getExtensionList(contactListExtensionID);
 
   const customerData = tuiContactExtensionList.find((extension: any) => {
     const { name, accountList = [] } = extension.data || {};
-    return name === "customer" && accountList.length > 0;
+    return name === 'customer' && accountList.length > 0;
   });
 
   if (customerData) {
@@ -342,26 +279,26 @@ function onCustomerServiceCommercialPluginUpdated(isEnabled: boolean) {
             list: res.data.map((item: any, index: number) => {
               return {
                 ...item,
-                renderKey: generateRenderKey("customerList", item, index),
+                renderKey: generateRenderKey('customerList', item, index),
                 infoKeyList: [],
-                btnKeyList: ["enterC2CConversation"],
+                btnKeyList: ['enterC2CConversation'],
               };
             }),
-            key: "customerList",
+            key: 'customerList',
           };
           contactListMap.value = { ...contactListMap.value, customerList };
         }
       })
-      .catch(() => {});
+      .catch(() => { });
   }
 }
 
 function onGroupListUpdated(groupList: IGroupModel[]) {
-  updateContactListMap("groupList", groupList);
+  updateContactListMap('groupList', groupList);
 }
 
 function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
-  updateContactListMap("blackList", userBlacklist);
+  updateContactListMap('blackList', userBlacklist);
 }
 
 function onFriendApplicationUnreadCountUpdated(friendApplicationUnreadCount: number) {
@@ -369,54 +306,37 @@ function onFriendApplicationUnreadCountUpdated(friendApplicationUnreadCount: num
 }
 
 function onFriendListUpdated(friendList: Friend[]) {
-  updateContactListMap("friendList", friendList);
+  updateContactListMap('friendList', friendList);
 }
 
 function onFriendApplicationListUpdated(friendApplicationList: FriendApplication[]) {
-  updateContactListMap("friendApplicationList", friendApplicationList);
+  updateContactListMap('friendApplicationList', friendApplicationList);
 }
 
 function updateContactListMap(key: keyof IContactList, list: IContactInfoType[]) {
   contactListMap.value[key].list = list;
-  contactListMap.value[key].list.map(
-    (item: IContactInfoType, index: number) =>
-      (item.renderKey = generateRenderKey(key, item, index))
-  );
+  contactListMap.value[key].list.map((item: IContactInfoType, index: number) => item.renderKey = generateRenderKey(key, item, index));
   updateCurrentContactInfoFromList(contactListMap.value[key].list, key);
 }
 
-function updateCurrentContactInfoFromList(
-  list: IContactInfoType[],
-  type: keyof IContactList
-) {
+function updateCurrentContactInfoFromList(list: IContactInfoType[], type: keyof IContactList) {
   if (
-    !(currentContactInfo.value as Friend)?.userID &&
-    !(currentContactInfo.value as IGroupModel)?.groupID
+    !(currentContactInfo.value as Friend)?.userID
+    && !(currentContactInfo.value as IGroupModel)?.groupID
   ) {
     return;
   }
   if (type === currentContactListKey.value || contactSearchingStatus.value) {
-    currentContactInfo.value =
-      list?.find(
-        (item: any) =>
-          (item?.groupID &&
-            item?.groupID === (currentContactInfo.value as IGroupModel)?.groupID) ||
-          (item?.userID && item?.userID === (currentContactInfo.value as Friend)?.userID)
-      ) || ({} as IContactInfoType);
-    TUIStore.update(StoreName.CUSTOM, "currentContactInfo", currentContactInfo.value);
+    currentContactInfo.value = list?.find(
+      (item: any) =>
+        (item?.groupID && item?.groupID === (currentContactInfo.value as IGroupModel)?.groupID) || (item?.userID && item?.userID === (currentContactInfo.value as Friend)?.userID),
+    ) || {} as IContactInfoType;
+    TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
   }
 }
 
-function generateRenderKey(
-  contactListMapKey: keyof IContactList,
-  contactInfo: IContactInfoType,
-  index: number
-) {
-  return `${contactListMapKey}-${
-    (contactInfo as Friend).userID ||
-    (contactInfo as IGroupModel).groupID ||
-    "index" + index
-  }`;
+function generateRenderKey(contactListMapKey: keyof IContactList, contactInfo: IContactInfoType, index: number) {
+  return `${contactListMapKey}-${(contactInfo as Friend).userID || (contactInfo as IGroupModel).groupID || ('index' + index)}`;
 }
 
 function onCurrentContactSearchResultUpdated(searchResult: IContactSearchResult) {
@@ -425,8 +345,8 @@ function onCurrentContactSearchResultUpdated(searchResult: IContactSearchResult)
 
 function onCurrentContactSearchingStatusUpdated(searchingStatus: boolean) {
   contactSearchingStatus.value = searchingStatus;
-  TUIStore.update(StoreName.CUSTOM, "currentContactInfo", {} as IContactInfoType);
-  TUIStore.update(StoreName.CUSTOM, "currentContactListKey", "");
+  TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
+  TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
 }
 
 function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
@@ -437,7 +357,7 @@ function onCurrentContactListKeyUpdated(contactListKey: string) {
   currentContactListKey.value = contactListKey;
 }
 
-provide("userOnlineStatusMap", userOnlineStatusMap);
+provide('userOnlineStatusMap', userOnlineStatusMap);
 </script>
 
 <style lang="scss" scoped src="./style/index.scss"></style>

+ 2 - 20
TUIKit/components/TUIContact/contact-list/style/web.scss

@@ -1,21 +1,3 @@
-.item {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 10px;
-  border-bottom: 1px solid #f1f1f1;
-
-  img {
-    width: 94rpx;
-    height: 94rpx;
-  }
-
-  .title {
-    margin-left: 10px;
-  }
-}
-
-
 .tui-contact-list {
   flex: 1;
   display: flex;
@@ -70,7 +52,7 @@
     &-main {
       padding: 0 15px !important;
 
-      &.hidden {
+      &.hidden{
         display: none;
       }
 
@@ -100,4 +82,4 @@
     font-size: 14px;
     color: #999;
   }
-}
+}

+ 2 - 2
TUIKit/components/TUIContact/contact-search/index.vue

@@ -66,7 +66,7 @@ import {
 } from '@tencentcloud/chat-uikit-engine';
 import TUICore, { TUIConstants } from '@tencentcloud/tui-core';
 import { TUIGlobal } from '@tencentcloud/universal-api';
-import { debounce } from 'lodash';
+import debounce from 'lodash/debounce';
 import { isPC } from '../../../utils/env';
 import Icon from '../../common/Icon.vue';
 import addSVG from '../../../assets/icon/add.svg';
@@ -158,7 +158,7 @@ watch(
     immediate: true,
   },
 );
-
+// 全局公共方法
 TUIGlobal.updateContactSearch = search;
 TUIGlobal.closeSearching = () => {
   isSearching.value = false;

+ 7 - 16
TUIKit/components/TUIContact/index.vue

@@ -2,8 +2,8 @@
   <SelectFriend v-if="isShowSelectFriend" />
   <div v-else-if="isShowContactList" :class="['tui-contact', !isPC && 'tui-contact-h5']">
     <div :class="['tui-contact-left', !isPC && 'tui-contact-h5-left']">
-      <!-- <ContactSearch /> -->
-      <ContactList v-if="contact"
+      <ContactSearch />
+      <ContactList
         :class="['tui-contact-left-list', !isPC && 'tui-contact-h5-left-list']"
       />
     </div>
@@ -20,20 +20,19 @@ import { TUIStore, StoreName } from "@tencentcloud/chat-uikit-engine";
 import { TUIGlobal } from "@tencentcloud/universal-api";
 import { ref, watchEffect } from "../../adapter-vue";
 import { isPC, isUniFrameWork } from "../../utils/env";
-import addSVG from "../../assets/icon/add.svg";
-import Icon from "../common/Icon.vue";
+
 import SelectFriend from "./select-friend/index.vue";
 import ContactSearch from "./contact-search/index.vue";
 import ContactList from "./contact-list/index.vue";
 import ContactInfo from "./contact-info/index.vue";
-import { onLoad, onShow, onPullDownRefresh } from "@dcloudio/uni-app";
+
 const emits = defineEmits(["switchConversation"]);
-const contact=ref(true)
+
 const props = defineProps({
-  // web/h5 single page application display format, uniapp please ignore
+  // web/h5 单页面应用展示形式,uniapp 请忽略
   displayType: {
     type: String,
-    default: "contactList", // "contactList" /  "selectFriend"
+    default: "contactList", // "contactList" 关系链列表(默认) / "selectFriend" 好友选择列表
     require: false,
   },
 });
@@ -46,15 +45,7 @@ const isShowContactInfo = ref(true);
 watchEffect(() => {
   isShowContactList.value = props?.displayType !== "selectFriend";
 });
-onPullDownRefresh(() => {
-  contact.value=false
-  setTimeout(() => {
-    contact.value=true
-    uni.stopPullDownRefresh();
-  }, 200);
 
-
-});
 TUIStore.watch(StoreName.CUSTOM, {
   isShowSelectFriendComponent: (data: any) => {
     if (!isUniFrameWork && props?.displayType === "selectFriend") {

+ 38 - 24
TUIKit/components/TUIContact/utils/index.ts

@@ -10,15 +10,17 @@ import TUIChatEngine, {
 import { TUIGlobal } from '@tencentcloud/universal-api';
 import { Toast, TOAST_TYPE } from '../../common/Toast/index';
 
+// 解析 用户头像/群头像
 export const generateAvatar = (item: any): string => {
   return (
     item?.avatar
     || item?.profile?.avatar
     || (item?.groupID && 'https://web.sdk.qcloud.com/im/assets/images/Public.svg')
-    || 'https://bucket.sxdirectpurchase.com/wx/static/img/ImAvatar.png'
+    || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
   );
 };
 
+// 解析 用户名称/群名称
 export const generateName = (item: any): string => {
   return (
     item?.remark
@@ -31,6 +33,7 @@ export const generateName = (item: any): string => {
   );
 };
 
+// 解析 信息界面 用户名称/群名称
 export const generateContactInfoName = (item: any): string => {
   return (
     item?.name
@@ -42,12 +45,12 @@ export const generateContactInfoName = (item: any): string => {
   );
 };
 
-// Parse the basic information display content of the contactInfo module
-// Group information display: group ID group type
-// User information display: user ID personal signature
+// 解析 contactInfo 模块 基础信息展示内容
+// 群信息展示: 群ID 群类型
+// 用户信息展示: 用户ID 个性签名
 export const generateContactInfoBasic = (
   contactInfo: any,
-): any[] => {
+): Array<{ label: string; data: string }> => {
   const res = [
     {
       label: contactInfo?.groupID ? '群ID' : 'ID',
@@ -63,6 +66,7 @@ export const generateContactInfoBasic = (
   return res;
 };
 
+// 判断是否为申请
 export const isApplicationType = (info: any) => {
   return (
     info?.type === TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_TO_ME
@@ -70,6 +74,8 @@ export const isApplicationType = (info: any) => {
   );
 };
 
+// 好友相关
+// 判断是否为双向好友关系
 export const isFriend = (info: any): Promise<boolean> => {
   return new Promise((resolve, reject) => {
     if (info?.groupID || !info?.userID) {
@@ -86,19 +92,19 @@ export const isFriend = (info: any): Promise<boolean> => {
     })
       .then((res: any) => {
         switch (res?.data?.successUserIDList[0]?.relation) {
-          // No friend relationship: A does not have B in his friend list, and B does not have A in his friend list
+          // 无好友关系:A 的好友表中没有 B,B 的好友表中也没有 A
           case TUIChatEngine.TYPES.SNS_TYPE_NO_RELATION:
             resolve(false);
             break;
-          // Single-item friend: A has B in his friend list, but B does not have A in his friend list
+          // 单项好友:A 的好友表中有 B,但 B 的好友表中没有 A
           case TUIChatEngine.TYPES.SNS_TYPE_A_WITH_B:
             resolve(false);
             break;
-          // Single-item friend: A does not have B in his friend list, but B has A in his friend list
+          // 单项好友:A 的好友表中没有 B,但 B 的好友表中有 A
           case TUIChatEngine.TYPES.SNS_TYPE_B_WITH_A:
             resolve(false);
             break;
-          // Two-way friendship
+          // 双向好友关系
           case TUIChatEngine.TYPES.SNS_TYPE_BOTH_WAY:
             resolve(true);
             break;
@@ -114,7 +120,7 @@ export const isFriend = (info: any): Promise<boolean> => {
   });
 };
 
-// Change friend‘s remark
+// 修改好友备注 / change friend‘s remark
 export const updateFriendRemark = (userID: string, remark: string) => {
   // eslint-disable-next-line no-control-regex
   if (remark?.replace(/[^\u0000-\u00ff]/g, 'aa')?.length > 96) {
@@ -143,7 +149,7 @@ export const updateFriendRemark = (userID: string, remark: string) => {
     });
 };
 
-// Delete one friend
+// 删除某个好友 / delete one friend
 export const deleteFriend = (userID: string) => {
   TUIFriendService.deleteFriend({
     userIDList: [userID],
@@ -172,7 +178,7 @@ export const deleteFriend = (userID: string) => {
     });
 };
 
-// Add friend
+// 添加好友 / add friend
 export const addFriend = (params: AddFriendParams) => {
   TUIFriendService.addFriend(params)
     .then(() => {
@@ -190,7 +196,8 @@ export const addFriend = (params: AddFriendParams) => {
     });
 };
 
-// Enter conversation
+// 进入会话 / enter conversation
+// todo:后续抽离为切换会话服务
 export const enterConversation = (item: any) => {
   const conversationID = item?.groupID
     ? `GROUP${item?.groupID}`
@@ -206,7 +213,7 @@ export const enterConversation = (item: any) => {
   );
 };
 
-// Accept friend application
+// 同意好友申请 / accept friend application
 export const acceptFriendApplication = (userID: string) => {
   TUIFriendService.acceptFriendApplication({
     userID,
@@ -227,7 +234,7 @@ export const acceptFriendApplication = (userID: string) => {
     });
 };
 
-// Refuse friend application
+// 拒绝好友申请 / refuse friend application
 export const refuseFriendApplication = (userID: string) => {
   TUIFriendService.refuseFriendApplication(userID)
     .then(() => {
@@ -245,7 +252,9 @@ export const refuseFriendApplication = (userID: string) => {
     });
 };
 
-// Dismiss group
+// 群组相关
+// todo: 后续抽离为 TUIGroup/server 中以 extension 形式提供
+// 解散群聊 / dismiss group
 export const dismissGroup = (groupID: string) => {
   TUIGroupService.dismissGroup(groupID)
     .then(() => {
@@ -253,6 +262,10 @@ export const dismissGroup = (groupID: string) => {
         message: TUITranslateService.t('TUIContact.解散群聊成功'),
         type: TOAST_TYPE.SUCCESS,
       });
+      // 解散群聊后特殊情况:
+      // 如果当前为 TUIContact 搜索状态,即 currentContactSearchingStatus === true
+      // 且当前打开的为 搜索结果 中的 群聊信息界面
+      // 需要更新搜索结果相关更新数据
       TUIGlobal?.updateContactSearch && TUIGlobal?.updateContactSearch();
     })
     .catch((error: any) => {
@@ -264,7 +277,7 @@ export const dismissGroup = (groupID: string) => {
     });
 };
 
-// Quit group
+// 退出群聊 / quit group
 export const quitGroup = (groupID: string) => {
   TUIGroupService.quitGroup(groupID)
     .then(() => {
@@ -282,7 +295,7 @@ export const quitGroup = (groupID: string) => {
     });
 };
 
-// Join group
+// 申请加入群聊 / join group
 export const joinGroup = (groupID: string, applyMessage?: string) => {
   TUIGroupService.joinGroup({
     groupID,
@@ -290,19 +303,20 @@ export const joinGroup = (groupID: string, applyMessage?: string) => {
   } as JoinGroupParams)
     .then((imResponse: { data: { status?: string } }) => {
       switch (imResponse?.data?.status) {
-        case TUIChatEngine.TYPES.JOIN_STATUS_WAIT_APPROVAL: // Wait for administrator approval
+        case TUIChatEngine.TYPES.JOIN_STATUS_WAIT_APPROVAL: // 等待管理员同意
           Toast({
             message: TUITranslateService.t('TUIContact.等待管理员同意'),
             type: TOAST_TYPE.SUCCESS,
           });
           break;
-        case TUIChatEngine.TYPES.JOIN_STATUS_SUCCESS: // Join group successfully
+        case TUIChatEngine.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
+          // todo: 切换到群聊所在chat界面
           Toast({
             message: TUITranslateService.t('TUIContact.加群成功'),
             type: TOAST_TYPE.SUCCESS,
           });
           break;
-        case TUIChatEngine.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // Already in the group
+        case TUIChatEngine.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // 已经在群中
           Toast({
             message: TUITranslateService.t('TUIContact.您已是群成员'),
             type: TOAST_TYPE.SUCCESS,
@@ -321,7 +335,8 @@ export const joinGroup = (groupID: string, applyMessage?: string) => {
     });
 };
 
-// Add to blacklist
+// 以下为黑名单相关逻辑
+// 加入黑名单
 export const addToBlacklist = (userID: string, successCallBack?: () => void) => {
   TUIUserService.addToBlacklist({
     userIDList: [userID],
@@ -337,8 +352,7 @@ export const addToBlacklist = (userID: string, successCallBack?: () => void) =>
       });
     });
 };
-
-// Remove from Blacklist
+// 移除黑名单
 export const removeFromBlacklist = (
   userID: string,
   successCallBack?: () => void,

+ 5 - 5
TUIKit/components/TUIConversation/actions-menu/index.vue

@@ -120,7 +120,7 @@ const deleteConversationDialogTitle = computed(() => {
     : props.selectedConversation?.type === TUIChatEngine.TYPES.CONV_GROUP ? 'TUIConversation.删除后,将清空该群聊的消息记录' : '';
 });
 function checkExceedBounds() {
-  // When the component is initially rendered, it executes and self-checks whether the boundary exceeds the screen, and handles it in nextTick.
+  // 组件初始渲染时,执行并自检边界有没有超出屏幕,在nextTick中处理。
   nextTick(() => {
     if (isUniFrameWork) {
       // check exceed bounds
@@ -148,9 +148,9 @@ function checkExceedBounds() {
         })
         .exec();
     } else {
-      // Handling the situation where the native Vue menu is lower than the screen
+      // 处理原生Vue菜单低于屏幕的情况
       const rect = actionsMenuDomRef.value?.getBoundingClientRect();
-      // The PC side sets the position of actionsMenu according to the position of the mouse click, otherwise the default value of 167px is used
+      // PC端根据鼠标点击的位置设置actionsMenu的位置,否则使用默认值167px
       if (isPC && typeof props.actionsMenuPosition.left !== 'undefined') {
         _actionsMenuPosition.value.left = props.actionsMenuPosition.left;
       }
@@ -211,6 +211,7 @@ const updateShowDeleteConversationDialog = (isShow: boolean) => {
 .actions-menu {
   position: absolute;
   left: 164px;
+  cursor: pointer;
   border-radius: 8px;
   border: 1px solid #e0e0e0;
   box-shadow: 0 -4px 12px 0 rgba(0, 0, 0, 0.06);
@@ -219,8 +220,7 @@ const updateShowDeleteConversationDialog = (isShow: boolean) => {
   opacity: 0;
 
   .actions-menu-item {
-    cursor: pointer;
-    padding: 10px 20px;
+    padding: 7px 20px;
     font-size: 12px;
     word-break: keep-all;
   }

+ 4 - 4
TUIKit/components/TUIConversation/conversation-header/server.ts

@@ -10,7 +10,7 @@ export interface IMenuItem {
   text: string;
   data: {
     name: string;
-    children?: any[];
+    children?: Array<IMenuItem>;
   };
   listener?: {
     onClicked: (...args: any[]) => void;
@@ -27,7 +27,7 @@ export default class ConversationHeaderServer {
     return ConversationHeaderServer.instance;
   }
 
-  public getMenu(): any[] {
+  public getMenu(): Array<IMenuItem> {
     const list = this.generateMenuList();
     if (!isPC && list.length > 0) {
       return [{
@@ -41,7 +41,7 @@ export default class ConversationHeaderServer {
     return list;
   }
 
-  private generateMenuList(): any[] {
+  private generateMenuList(): Array<IMenuItem> {
     const list = [
       {
         icon: C2C,
@@ -68,7 +68,7 @@ export default class ConversationHeaderServer {
   }
 
   private createConversation(item: IMenuItem) {
-    // Create a conversation and notify conversationServer via TUICore.callService
+    // 创建会话,通过 TUICore.callService 通知 conversationServer
     TUICore.callService({
       serviceName: TUIConstants.TUIConversation.SERVICE.NAME,
       method: TUIConstants.TUIConversation.SERVICE.METHOD.CREATE_CONVERSATION,

BIN=BIN
TUIKit/components/TUIConversation/conversation-list/empaty.jpg


+ 98 - 52
TUIKit/components/TUIConversation/conversation-list/index.vue

@@ -7,7 +7,6 @@
       :selectedConversationDomRect="currentConversationDomRect"
       @closeConversationActionMenu="closeConversationActionMenu"
     />
-
     <div
       v-for="(conversation, index) in conversationList"
       :id="`convlistitem-${index}`"
@@ -25,13 +24,19 @@
             'tui-conversation-item-selected',
           conversation.isPinned && 'tui-conversation-item-pinned',
         ]"
-        @click="enterConversationChat(conversation.conversationID, conversation)"
+        @click="enterConversationChat(conversation.conversationID)"
         @longpress="showConversationActionMenu($event, conversation, index)"
-        @contextmenu="showConversationActionMenu($event, conversation, index, true)"
+        @contextmenu="
+          showConversationActionMenu($event, conversation, index, true)
+        "
       >
         <aside class="left">
-          <Avatar useSkeletonAnimation :url="conversation.getAvatar()" size="91rpx" />
-          <div
+          <Avatar
+            useSkeletonAnimation
+            :url="conversation.userProfile.avatar"
+            size="60px"
+          />
+          <!-- <div
             v-if="userOnlineStatusMap && isShowUserOnlineStatus(conversation)"
             :class="[
               'online-status',
@@ -39,13 +44,19 @@
               Object.keys(userOnlineStatusMap).includes(
                 conversation.userProfile.userID
               ) &&
-              userOnlineStatusMap[conversation.userProfile.userID].statusType === 1
+              userOnlineStatusMap[conversation.userProfile.userID]
+                .statusType === 1
                 ? 'online-status-online'
                 : 'online-status-offline',
             ]"
-          />
-          <span v-if="conversation.unreadCount > 0 && !conversation.isMuted" class="num">
-            {{ conversation.unreadCount > 99 ? "99+" : conversation.unreadCount }}
+          /> -->
+          <span
+            v-if="conversation.unreadCount > 0 && !conversation.isMuted"
+            class="num"
+          >
+            {{
+              conversation.unreadCount > 99 ? "99+" : conversation.unreadCount
+            }}
           </span>
           <span
             v-if="conversation.unreadCount > 0 && conversation.isMuted"
@@ -60,14 +71,6 @@
             <div class="middle-box">
               <span
                 v-if="
-                  conversation.draftText &&
-                  conversation.conversationID !== currentConversationID
-                "
-                class="middle-box-draft"
-                >{{ TUITranslateService.t("TUIChat.[草稿]") }}</span
-              >
-              <span
-                v-else-if="
                   conversation.type === 'GROUP' &&
                   conversation.groupAtInfoList &&
                   conversation.groupAtInfoList.length > 0
@@ -75,9 +78,9 @@
                 class="middle-box-at"
                 >{{ conversation.getGroupAtInfo() }}</span
               >
-              <div class="middle-box-content">
+              <p class="middle-box-content">
                 {{ conversation.getLastMessage("text") }}
-              </div>
+              </p>
             </div>
           </div>
           <div class="content-footer">
@@ -87,19 +90,10 @@
         </div>
       </div>
     </div>
-    <div
+    <empty-view
       v-if="conversationList.length == 0"
-      style="
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        height: 90vh;
-        justify-content: center;
-      "
-    >
-      <img src="./empaty.jpg" style="width: 200px; height: 150px" />
-      <span style="margin-top: 20px"> 暂无会话~</span>
-    </div>
+      title="暂无数据"
+    ></empty-view>
   </div>
 </template>
 
@@ -118,14 +112,18 @@ import TUIChatEngine, {
   TUIStore,
   StoreName,
   TUIConversationService,
-  TUITranslateService,
   IConversationModel,
 } from "@tencentcloud/chat-uikit-engine";
-import { TUIGlobal, isIOS, addLongPressListener } from "@tencentcloud/universal-api";
+import {
+  TUIGlobal,
+  isIOS,
+  addLongPressListener,
+} from "@tencentcloud/universal-api";
 import Icon from "../../common/Icon.vue";
 import Avatar from "../../common/Avatar/index.vue";
 import ActionsMenu from "../actions-menu/index.vue";
 import muteIcon from "../../../assets/icon/mute.svg";
+import emptyView from "@/components/empty-view.vue";
 import { isPC, isH5, isUniFrameWork, isMobile } from "../../../utils/env";
 
 const emits = defineEmits(["handleSwitchConversation", "getPassingRef"]);
@@ -134,6 +132,9 @@ const currentConversationID = ref<string>();
 const currentConversationDomRect = ref<DOMRect>();
 const isShowOverlay = ref<boolean>(false);
 const conversationList = ref<IConversationModel[]>([]);
+setTimeout(() => {
+  console.log(conversationList);
+}, 2000);
 const conversationListDomRef = ref<HTMLElement | undefined>();
 const conversationListInnerDomRef = ref<HTMLElement | undefined>();
 const actionsMenuPosition = ref<{
@@ -145,7 +146,7 @@ const actionsMenuPosition = ref<{
   left: undefined,
   conversationHeight: undefined,
 });
-const displayOnlineStatus = ref(false);
+const displayOnlineStatus = ref(false); // 在线状态 默认关闭
 const userOnlineStatusMap = ref<IUserStatusMap>();
 
 let lastestOpenActionsMenuTime: number | null = null;
@@ -157,6 +158,7 @@ onMounted(() => {
     currentConversation: onCurrentConversationUpdated,
   });
 
+  // 初始状态
   TUIStore.watch(StoreName.USER, {
     displayOnlineStatus: onDisplayOnlineStatusUpdated,
     userStatusList: onUserStatusListUpdated,
@@ -174,6 +176,7 @@ onUnmounted(() => {
     currentConversation: onCurrentConversationUpdated,
   });
 
+  // 初始状态
   TUIStore.unwatch(StoreName.USER, {
     displayOnlineStatus: onDisplayOnlineStatusUpdated,
     userStatusList: onUserStatusListUpdated,
@@ -181,7 +184,10 @@ onUnmounted(() => {
 });
 
 const isShowUserOnlineStatus = (conversation: IConversationModel): boolean => {
-  return displayOnlineStatus.value && conversation.type === TUIChatEngine.TYPES.CONV_C2C;
+  return (
+    displayOnlineStatus.value &&
+    conversation.type === TUIChatEngine.TYPES.CONV_C2C
+  );
 };
 
 const showConversationActionMenu = (
@@ -202,8 +208,11 @@ const showConversationActionMenu = (
 };
 
 const closeConversationActionMenu = () => {
-  // Prevent continuous triggering of overlay tap events
-  if (lastestOpenActionsMenuTime && Date.now() - lastestOpenActionsMenuTime > 300) {
+  // 防止连续触发overlay的tap事件
+  if (
+    lastestOpenActionsMenuTime &&
+    Date.now() - lastestOpenActionsMenuTime > 300
+  ) {
     currentConversation.value = undefined;
     isShowOverlay.value = false;
   }
@@ -214,13 +223,15 @@ const getActionsMenuPosition = (event: Event, index: number) => {
     if (typeof conversationListDomRef.value === "undefined") {
       emits("getPassingRef", conversationListDomRef);
     }
-    const query = TUIGlobal?.createSelectorQuery().in(conversationListDomRef.value);
+    const query = TUIGlobal?.createSelectorQuery().in(
+      conversationListDomRef.value
+    );
     query
       .select(`#convlistitem-${index}`)
       .boundingClientRect((data) => {
         if (data) {
           actionsMenuPosition.value = {
-            // The uni-page-head of uni-h5 is not considered a member of the viewport, so the height of the head is manually increased.
+            // uni-h5的uni-page-head不被认为是视窗中的成员,因此手动上head的高度
             top: data.bottom + (isH5 ? 44 : 0),
             // @ts-expect-error in uniapp event has touches property
             left: event.touches[0].pageX,
@@ -231,9 +242,11 @@ const getActionsMenuPosition = (event: Event, index: number) => {
       })
       .exec();
   } else {
+    // 处理Vue原生
     const rect =
-      ((event.currentTarget || event.target) as HTMLElement)?.getBoundingClientRect() ||
-      {};
+      (
+        (event.currentTarget || event.target) as HTMLElement
+      )?.getBoundingClientRect() || {};
     if (rect) {
       actionsMenuPosition.value = {
         top: rect.bottom,
@@ -245,7 +258,7 @@ const getActionsMenuPosition = (event: Event, index: number) => {
   }
 };
 
-const enterConversationChat = (conversationID: string, a) => {
+const enterConversationChat = (conversationID: string) => {
   emits("handleSwitchConversation", conversationID);
   TUIConversationService.switchConversation(conversationID);
 };
@@ -257,9 +270,9 @@ function addLongPressHandler() {
   addLongPressListener({
     element: conversationListInnerDomRef.value,
     onLongPress: (event, target) => {
-      const index = (Array.from(
-        conversationListInnerDomRef.value!.children
-      ) as HTMLElement[]).indexOf(target!);
+      const index = (
+        Array.from(conversationListInnerDomRef.value!.children) as HTMLElement[]
+      ).indexOf(target!);
       showConversationActionMenu(event, conversationList.value[index], index);
     },
     options: {
@@ -275,10 +288,9 @@ function onCurrentConversationUpdated(conversation: IConversationModel) {
 }
 
 function onConversationListUpdated(list: IConversationModel[]) {
-  console.log("222222", list);
+  console.log(list);
 
   conversationList.value = list;
-  console.log("1111", conversationList.value);
 }
 
 function onCurrentConversationIDUpdated(id: string) {
@@ -291,18 +303,48 @@ function onDisplayOnlineStatusUpdated(status: boolean) {
 
 function onUserStatusListUpdated(list: Map<string, IUserStatus>) {
   if (list.size !== 0) {
-    userOnlineStatusMap.value = [...list.entries()].reduce((obj, [key, value]) => {
-      obj[key] = value;
-      return obj;
-    }, {} as IUserStatusMap);
+    userOnlineStatusMap.value = [...list.entries()].reduce(
+      (obj, [key, value]) => {
+        obj[key] = value;
+        return obj;
+      },
+      {} as IUserStatusMap
+    );
   }
 }
-// Expose to the parent component and close actionsMenu when a sliding event is detected
+// 暴露给父组件,当监听到滑动事件时关闭actionsMenu
 defineExpose({ closeChildren: closeConversationActionMenu });
 </script>
 
 <style lang="scss" scoped src="./style/index.scss"></style>
 <style lang="scss" scoped>
+.tui-conversation-item {
+  padding: 12px;
+  display: flex;
+  cursor: pointer;
+  height: 73px;
+  flex-direction: row;
+  align-items: center;
+  .content-header {
+    .name {
+      font-weight: 500;
+      font-size: 34upx;
+      color: #333333;
+    }
+    .middle-box-content {
+      font-weight: 500;
+      font-size: 30upx;
+      color: #666666;
+    }
+  }
+  .content {
+    line-height: 35px;
+  }
+  .left {
+    width: auto;
+    height: auto;
+  }
+}
 .disable-select {
   -webkit-touch-callout: none;
   -webkit-user-select: none;
@@ -311,4 +353,8 @@ defineExpose({ closeChildren: closeConversationActionMenu });
   -ms-user-select: none;
   user-select: none;
 }
+::v-deep .empty-box {
+  padding-top: 20%;
+  background: #f4f4f4;
+}
 </style>

+ 1 - 1
TUIKit/components/TUIConversation/conversation-list/style/color.scss

@@ -34,7 +34,7 @@
     }
 
     .middle-box {
-      &-at, &-draft {
+      &-at {
         color: #fb5059 !important;
         font-family: PingFangSC-Regular;
         font-weight: 400;

+ 10 - 0
TUIKit/components/TUIConversation/conversation-list/style/h5.scss

@@ -17,10 +17,20 @@
 
     .tui-conversation-item {
       -webkit-touch-callout: none;
+
+      /* 系统默认菜单被禁用 */
       -webkit-user-select: none;
+
+      /* webkit浏览器 */
       -khtml-user-select: none;
+
+      /* 早期浏览器 */
       -moz-user-select: none;
+
+      /* 火狐 */
       -ms-user-select: none;
+
+      /* IE10 */
       user-select: none;
 
       .content {

+ 9 - 28
TUIKit/components/TUIConversation/conversation-list/style/web.scss

@@ -4,6 +4,7 @@
   letter-spacing: 0;
   flex: 1;
   overflow: auto;
+  background: #f4f4f4;
 }
 
 .tui-conversation {
@@ -12,13 +13,11 @@
     display: flex;
     align-items: center;
     cursor: pointer;
-    box-sizing: border-box;
-    overflow: hidden;
 
     .left {
       position: relative;
-      width: 91rpx;
-        height: 91rpx;
+      width: 36px;
+      height: 36px;
 
       .num {
         position: absolute;
@@ -78,7 +77,7 @@
     .content-footer {
       line-height: 16px;
       display: flex;
-      flex-direction: column;
+      // flex-direction: column;
 
       .time {
         font-size: 12px;
@@ -93,11 +92,9 @@
       flex: 1;
       padding-left: 8px;
       justify-content: space-between;
-      box-sizing: border-box;
-      overflow: hidden;
 
       .content-footer {
-        align-items: flex-end;
+        align-items: center;
 
         .icon {
           display: inline-block;
@@ -112,31 +109,16 @@
       flex: 1;
       display: flex;
       flex-direction: column;
-      overflow: hidden;
-      box-sizing: border-box;
 
       &-label {
         flex: 1;
         font-size: 14px;
       }
 
-      .middle-box-content {
-        font-weight: 400;
-        font-size: 28rpx !important;
-        color: #999999;
-        font-style: normal;
-      }
-
       .name {
-        width: 404rpx;
-        height: 44rpx;
-        font-weight: 500;
-        font-size: 32rpx;
-        color: #333333;
-        line-height: 40rpx;
-        // width: 110px;
+        width: 400upx;
         letter-spacing: 0;
-        // font-size: 14px;
+        font-size: 14px;
         overflow: hidden;
         text-overflow: ellipsis;
         white-space: nowrap;
@@ -147,8 +129,7 @@
         display: flex;
         align-items: center;
 
-        &-at,
-        &-draft {
+        &-at {
           font-size: 12px;
         }
 
@@ -196,4 +177,4 @@
       }
     }
   }
-}
+}

+ 0 - 2
TUIKit/components/TUIConversation/entry.ts

@@ -1,2 +0,0 @@
-import { TUIChatKit } from '../../index.ts';
-TUIChatKit?.init(); // Add optional chaining operator to fix sample main package integration errors

+ 43 - 203
TUIKit/components/TUIConversation/index.vue

@@ -1,109 +1,43 @@
 <template>
-  <div>
-    <div
-      class="tui-conversation"
-      @click="handleClickConv"
-      @touchstart="handleTouchStart"
-      @touchend="handleTouchEnd"
-    >
-      <div class="header">
-        <div class="item" @click="group">
-          <img
-            src="https://directpurchase-oss-dev.oss-cn-chengdu.aliyuncs.com/wx/static/img/jhq.png"
-            style="width: 60px; height: 60px"
-          />
-          <span class="title">资源聚合群</span>
-        </div>
-        <div class="item" @click="book">
-          <img
-            src="https://directpurchase-oss-dev.oss-cn-chengdu.aliyuncs.com/wx/static/img/txl.png"
-            style="width: 60px; height: 60px"
-          />
-          <span class="title">通讯录</span>
-        </div>
-        <div class="item" @click="interactive">
-          <div class="count" v-if="hdxsCount > 0">{{ hdxsCount }}</div>
-          <img
-            src="https://directpurchase-oss-dev.oss-cn-chengdu.aliyuncs.com/wx/static/img/hdxx.png"
-            style="width: 60px; height: 60px"
-          />
-          <span class="title">互动消息</span>
-        </div>
-        <div class="item" @click="serviceNotice">
-          <div class="count" v-if="fwCount > 0">{{ fwCount }}</div>
-          <img
-            src="https://directpurchase-oss-dev.oss-cn-chengdu.aliyuncs.com/wx/static/img/fwtz.png"
-            style="width: 60px; height: 60px"
-          />
-          <span class="title">服务通知</span>
-        </div>
-      </div>
-      <!--     
-      <TUISearch searchType="global" />
-      <ConversationHeader v-if="isShowConversationHeader" ref="headerRef" />
-      <ConversationNetwork /> -->
-      <!-- <div
-        v-if="!userId"
-        style="
-          display: flex;
-          flex-direction: column;
-          align-items: center;
-          height: 90vh;
-          justify-content: center;
-        "
-      >
-        <img src="@/static/empaty.jpg" style="width: 200px; height: 150px" />
-        <span style="margin-top: 20px"> 暂无会话~</span>
-      </div> -->
-
-      <ConversationList
-        ref="conversationListDomRef"
-        class="tui-conversation-list"
-        @handleSwitchConversation="handleSwitchConversation"
-        @getPassingRef="getPassingRef"
-      />
-    </div>
-    <div></div>
+  <div
+    class="tui-conversation"
+    @click="handleClickConv"
+    @touchstart="handleTouchStart"
+    @touchend="handleTouchEnd"
+  >
+    <TUISearch searchType="global" />
+    <!-- <ConversationHeader
+      v-if="isShowConversationHeader"
+      ref="headerRef"
+    /> -->
+    <ConversationNetwork />
+    <ConversationList
+      ref="conversationListDomRef"
+      class="tui-conversation-list"
+      @handleSwitchConversation="handleSwitchConversation"
+      @getPassingRef="getPassingRef"
+    />
   </div>
 </template>
 <script lang="ts" setup>
-import { TUIStore, StoreName, TUIChatService } from "@tencentcloud/chat-uikit-engine";
+import { TUIStore, StoreName } from "@tencentcloud/chat-uikit-engine";
 import { TUIGlobal } from "@tencentcloud/universal-api";
-import { ref, onMounted } from "../../adapter-vue";
-// import TUISearch from "../TUISearch/index.vue";
+import { ref } from "../../adapter-vue";
+import TUISearch from "../TUISearch/index.vue";
 import ConversationList from "./conversation-list/index.vue";
-import * as msgApi from "@/api/message/index";
-// import ConversationHeader from "./conversation-header/index.vue";
+import ConversationHeader from "./conversation-header/index.vue";
 import ConversationNetwork from "./conversation-network/index.vue";
-import { onHide, onShow, onLoad } from "@dcloudio/uni-app";
-// #ifdef MP-WEIXIN
-// uniapp packaged mini-programs are integrated by default, and the default initialization entry file is imported here
-// TUIChatKit init needs to be encapsulated because uni vue2 will report an error when compiling H5 directly through conditional compilation
-import "./entry.ts";
-// #endif
-const centerDialogVisibl = ref(false);
+import { onHide } from "@dcloudio/uni-app"; // 该方法只能用在父组件内,子组件内不生效
+
 const emits = defineEmits(["handleSwitchConversation"]);
+
 const totalUnreadCount = ref(0);
 const headerRef = ref<typeof ConversationHeader>();
 const conversationListDomRef = ref<typeof ConversationList>();
 const touchX = ref<number>(0);
 const touchY = ref<number>(0);
 const isShowConversationHeader = ref<boolean>(true);
-onLoad((option) => {
-  initCount();
-  initCountServer();
-});
-const userId = uni.getStorageSync("userid");
-const hdxsCount = ref("");
-const fwCount = ref("");
-async function initCount() {
-  let res = await msgApi.default.nuRead(1);
-  hdxsCount.value = res.data;
-}
-async function initCountServer() {
-  let res = await msgApi.default.nuRead(3);
-  fwCount.value = res.data;
-}
+
 TUIStore.watch(StoreName.CONV, {
   totalUnreadCount: (count: number) => {
     totalUnreadCount.value = count;
@@ -116,90 +50,20 @@ TUIStore.watch(StoreName.CUSTOM, {
   },
 });
 
-const interactive = () => {
-  TUIGlobal?.navigateTo({
-    url: "/pages/message/interactive",
-  });
-};
-const serviceNotice = () => {
-  TUIGlobal?.navigateTo({
-    url: "/pages/message/serviceNotice",
-  });
-};
-const group = () => {
-  TUIGlobal?.navigateTo({
-    url: "/pages/group/index",
-  });
-};
+const handleSwitchConversation = (conversationID: string) => {
+  let companyUserId =
+    conversationID.indexOf("A") > -1
+      ? conversationID.substring(4)
+      : conversationID.substring(3);
 
-const book = () => {
   TUIGlobal?.navigateTo({
-    url: "/TUIKit/components/TUIContact/index",
+    url:
+      "/TUIKit/components/TUIChat/index?companyUserId=" +
+      companyUserId +
+      "&recruitUserId=" +
+      uni.getStorageSync("userId"),
   });
-};
-
-function getQueryString(name) {
-  var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
-  var r = window.location.search.substr(1).match(reg);
-  if (r != null) {
-    return unescape(r[2]);
-  }
-  return null;
-}
-const spuInfo = ref();
-const handleSwitchConversation = async (conversationID: string) => {
-  let spuId = getQueryString("spuId");
-  let share = getQueryString("share");
-  if (spuId && share === "1") {
-    let res = await msgApi.default.getSupplyDetails({ spuId: spuId });
-    console.log(res);
-    spuInfo.value = res.data;
-    uni.showModal({
-      title: "提示",
-      content: `是否确认分享给该好友`,
-      success: function (res) {
-        if (res.confirm) {
-          console.log("用户点击确定");
-          let promise = TUIChatService.sendCustomMessage({
-            payload: {
-              data: JSON.stringify({
-                saleType: spuInfo.value.saleType,
-                id: spuInfo.value.id,
-                // 订单类消息标识字段
-                businessID: "order",
-                // 订单标题
-                title: spuInfo.value.title,
-                // 订单描述
-                description: spuInfo.value.spuDesc,
-                // 订单价格
-                price: spuInfo.value.lowestPrice,
-                // 订单 url
-                link: "https://buy.cloud.tencent.com/avc?ver=ultimate",
-                // 订单封面图
-                imageUrl: spuInfo.value.pic,
-                unit: spuInfo.value.unit,
-              }),
-              description: "",
-              extension: "",
-            },
-          });
-          console.log("promise", promise);
-
-          TUIGlobal?.navigateTo({
-            url: "/TUIKit/components/TUIChat/index",
-          });
-          emits("handleSwitchConversation", conversationID);
-        } else if (res.cancel) {
-          console.log("用户点击取消");
-        }
-      },
-    });
-  } else {
-    TUIGlobal?.navigateTo({
-      url: "/TUIKit/components/TUIChat/index",
-    });
-    emits("handleSwitchConversation", conversationID);
-  }
+  emits("handleSwitchConversation", conversationID);
 };
 
 const closeChildren = () => {
@@ -223,20 +87,20 @@ const handleTouchEnd = (e: any) => {
   const y = e.changedTouches[0].clientY;
   let turn = "";
   if (x - touchX.value > 50 && Math.abs(y - touchY.value) < 50) {
-    // Swipe right
+    // 右滑
     turn = "right";
   } else if (x - touchX.value < -50 && Math.abs(y - touchY.value) < 50) {
-    // Swipe left
+    // 左滑
     turn = "left";
   }
   if (y - touchY.value > 50 && Math.abs(x - touchX.value) < 50) {
-    // Swipe down
+    // 下滑
     turn = "down";
   } else if (y - touchY.value < -50 && Math.abs(x - touchX.value) < 50) {
-    // Swipe up
+    // 上滑
     turn = "up";
   }
-  // Operate according to the direction
+  // 根据方向进行操作
   if (turn === "down" || turn === "up") {
     closeChildren();
   }
@@ -246,29 +110,5 @@ const getPassingRef = (ref) => {
   ref.value = conversationListDomRef.value;
 };
 </script>
-<style lang="less" scoped>
-.title {
-  font-family: PingFangSC, PingFang SC;
-  font-weight: 500;
-  font-size: 24rpx;
-  color: #333333;
-  margin-top: 10rpx;
-}
-.count {
-  position: absolute;
-  right: 0px;
-  top: -4px;
-  width: 18px;
-  height: 18px;
-  background: red;
-  text-align: center;
-  color: white;
-  border-radius: 50%;
-  display: flex;
-  flex-direction: row;
-  justify-content: center;
-  align-items: center;
-  font-size: 10px;
-}
-</style>
+
 <style lang="scss" scoped src="./style/index.scss"></style>

+ 0 - 0
TUIKit/components/TUIConversation/merChantSideIndex.vue


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio