Bladeren bron

消息插件

潘超林 1 maand geleden
bovenliggende
commit
ba6c61c618
100 gewijzigde bestanden met toevoegingen van 2959 en 0 verwijderingen
  1. 5 0
      TUIKit/.npmignore
  2. 3 0
      TUIKit/.npmrc
  3. 136 0
      TUIKit/CHANGELOG.md
  4. 56 0
      TUIKit/README.md
  5. 14 0
      TUIKit/adapter-vue.ts
  6. 7 0
      TUIKit/assets/icon/add-circle.svg
  7. 12 0
      TUIKit/assets/icon/add.svg
  8. 12 0
      TUIKit/assets/icon/at.svg
  9. 6 0
      TUIKit/assets/icon/audio.svg
  10. 16 0
      TUIKit/assets/icon/back.svg
  11. 6 0
      TUIKit/assets/icon/call-video.svg
  12. 6 0
      TUIKit/assets/icon/call-voice.svg
  13. BIN
      TUIKit/assets/icon/call.png
  14. BIN
      TUIKit/assets/icon/camera-uni.png
  15. 23 0
      TUIKit/assets/icon/cancel.svg
  16. 3 0
      TUIKit/assets/icon/check-sm.svg
  17. 3 0
      TUIKit/assets/icon/close-dark.svg
  18. BIN
      TUIKit/assets/icon/close-image.png
  19. 7 0
      TUIKit/assets/icon/convertText_en.svg
  20. 8 0
      TUIKit/assets/icon/convertText_zh.svg
  21. 5 0
      TUIKit/assets/icon/d-left-arrow.svg
  22. 5 0
      TUIKit/assets/icon/d-right-arrow.svg
  23. 25 0
      TUIKit/assets/icon/del-icon.svg
  24. 1 0
      TUIKit/assets/icon/double-arrow.svg
  25. 3 0
      TUIKit/assets/icon/down-icon.svg
  26. BIN
      TUIKit/assets/icon/downaload-image.png
  27. 6 0
      TUIKit/assets/icon/download.svg
  28. 7 0
      TUIKit/assets/icon/edit.svg
  29. 10 0
      TUIKit/assets/icon/evaluate.svg
  30. BIN
      TUIKit/assets/icon/face-uni.png
  31. BIN
      TUIKit/assets/icon/face.png
  32. BIN
      TUIKit/assets/icon/files.png
  33. 6 0
      TUIKit/assets/icon/forward-each.svg
  34. 8 0
      TUIKit/assets/icon/forward-merge.svg
  35. 7 0
      TUIKit/assets/icon/icon-arrow-left.svg
  36. 36 0
      TUIKit/assets/icon/icon-c2c.svg
  37. 6 0
      TUIKit/assets/icon/icon-close.svg
  38. BIN
      TUIKit/assets/icon/image-uni.png
  39. BIN
      TUIKit/assets/icon/image.png
  40. 3 0
      TUIKit/assets/icon/input-close.svg
  41. 6 0
      TUIKit/assets/icon/left-arrow.svg
  42. BIN
      TUIKit/assets/icon/loading.gif
  43. BIN
      TUIKit/assets/icon/loading.png
  44. 6 0
      TUIKit/assets/icon/minus.svg
  45. BIN
      TUIKit/assets/icon/more-uni.png
  46. BIN
      TUIKit/assets/icon/more.png
  47. 1 0
      TUIKit/assets/icon/msg-audio.svg
  48. 30 0
      TUIKit/assets/icon/msg-copy.svg
  49. 33 0
      TUIKit/assets/icon/msg-del.svg
  50. 31 0
      TUIKit/assets/icon/msg-forward.svg
  51. 8 0
      TUIKit/assets/icon/msg-quote.svg
  52. 29 0
      TUIKit/assets/icon/msg-revoke.svg
  53. 5 0
      TUIKit/assets/icon/multiple-select.svg
  54. 8 0
      TUIKit/assets/icon/mute.svg
  55. 7 0
      TUIKit/assets/icon/plus.svg
  56. 6 0
      TUIKit/assets/icon/radio.svg
  57. 14 0
      TUIKit/assets/icon/right-arrow.svg
  58. 3 0
      TUIKit/assets/icon/right-icon.svg
  59. 7 0
      TUIKit/assets/icon/rotate-left.svg
  60. 7 0
      TUIKit/assets/icon/rotate-right.svg
  61. 52 0
      TUIKit/assets/icon/search-default.svg
  62. 7 0
      TUIKit/assets/icon/search-more.svg
  63. 5 0
      TUIKit/assets/icon/search.svg
  64. 27 0
      TUIKit/assets/icon/selected.svg
  65. 8 0
      TUIKit/assets/icon/setting.svg
  66. BIN
      TUIKit/assets/icon/star-light.png
  67. BIN
      TUIKit/assets/icon/star.png
  68. 6 0
      TUIKit/assets/icon/start-group.svg
  69. 12 0
      TUIKit/assets/icon/translate.svg
  70. BIN
      TUIKit/assets/icon/video-play.png
  71. BIN
      TUIKit/assets/icon/video-uni.png
  72. BIN
      TUIKit/assets/icon/video.png
  73. 9 0
      TUIKit/assets/icon/words.svg
  74. 9 0
      TUIKit/assets/icon/zoom-in.svg
  75. 9 0
      TUIKit/assets/icon/zoom-out.svg
  76. 59 0
      TUIKit/assets/styles/common.scss
  77. 99 0
      TUIKit/assets/styles/sample.scss
  78. 62 0
      TUIKit/components/TUIChat/chat-header/index.vue
  79. 73 0
      TUIKit/components/TUIChat/config.ts
  80. 15 0
      TUIKit/components/TUIChat/emoji-config/custom-emoji.ts
  81. 114 0
      TUIKit/components/TUIChat/emoji-config/default-emoji.ts
  82. 140 0
      TUIKit/components/TUIChat/emoji-config/index.ts
  83. 66 0
      TUIKit/components/TUIChat/emoji-config/locales/en.ts
  84. 66 0
      TUIKit/components/TUIChat/emoji-config/locales/zh_cn.ts
  85. 35 0
      TUIKit/components/TUIChat/entry-chat-only.ts
  86. 159 0
      TUIKit/components/TUIChat/forward/index.vue
  87. 6 0
      TUIKit/components/TUIChat/index.ts
  88. 298 0
      TUIKit/components/TUIChat/index.vue
  89. 297 0
      TUIKit/components/TUIChat/indexlink.vue
  90. 180 0
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue
  91. 2 0
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/index.ts
  92. 78 0
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/index.vue
  93. 25 0
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/h5.scss
  94. 4 0
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/index.scss
  95. 55 0
      TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/web.scss
  96. 2 0
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/index.ts
  97. 208 0
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/index.vue
  98. 57 0
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/color.scss
  99. 63 0
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/h5.scss
  100. 0 0
      TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/index.scss

+ 5 - 0
TUIKit/.npmignore

@@ -0,0 +1,5 @@
+node_modules
+package-lock.json
+pnpm-lock.yaml
+dist
+**/dist/

+ 3 - 0
TUIKit/.npmrc

@@ -0,0 +1,3 @@
+auto-install-peers=true
+strict-peer-dependencies=false
+# shamefully-hoist=true

+ 136 - 0
TUIKit/CHANGELOG.md

@@ -0,0 +1,136 @@
+## [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
+- TUIKit 适配 uniapp cli
+- 点击空白区域时收起小表情面板和工具栏
+- callkit 提供音视频通话中途加人能力 不包括app平台
+
+### Fix
+- 修复视频一定概率无法播放的问题
+- 修复消息引用的视觉左侧未对齐的问题
+
+## [2.1.1] (2024-04-26)
+
+### Features
+- 支持语音转文字
+- 文本消息转翻译兼容小表情上屏,兼容提及所有人
+
+### Fix
+- 优化已读回执详情列表超长昵称的显示效果
+- 解决转发消息已读回执失效的问题
+- 解决加没有群申请时额外请求用户信息的问题
+
+## [2.1.0] (2024-04-12)
+
+### Features
+- 新增消息翻译功能
+
+### Fix
+- uniapp 修复选人组件搜索失效问题
+
+## [2.0.9] (2024-03-29)
+
+### Features
+- ScrollButton 支持未读新消息提示
+- 群未决申请展示优化
+
+### Update
+- 下线本地审核相关入口
+
+### Fix
+- 修复 nick 过长样式溢出问题
+
+## [2.0.8] (2024-03-15)
+
+### Fix
+- 修复已知问题,提升稳定性
+
+## [2.0.7] (2024-03-15)
+
+### Features
+- 会话列表菜单出现位置跟随手指
+
+### Fix
+- 修复一定概率下图片无法预览的问题
+
+## [2.0.6] (2024-03-01)
+
+### Features
+
+- 升级 universal api 引入方式
+- 支持 TUISearch 消息云端搜索
+
+## [2.0.5] (2024-02-04)
+
+### Features
+
+- 聊天界面更新黄脸小表情
+- 添加音频播放动画
+
+### Fix
+
+- 语音场景优化 修复了语音播放相关的体验问题
+
+## [2.0.4] (2024-01-19)
+
+### Features
+
+- 支持文本消息复制
+
+### Fix
+
+- 修复 uniapp 打包 APP、小程序 语音首次发送失败问题
+
+## [2.0.3] (2024-01-12)
+
+### Features
+
+- TUIContact 关系链支持用户在线状态。
+- TUIContact 中获取客服列表的时机调整为 Engine 设置商业化能力位之后。
+
+### Fix
+
+- 修复已知问题,提升稳定性
+
+## [2.0.2] (2024-01-05)
+
+### Fix
+
+- 修复已知问题,提升稳定性
+
+## [2.0.0] (2023-12-21)
+
+### Features
+
+- 全面支持 uniapp Vue2 & uniapp Vue3,包括以下主体功能:
+  - TUIChat: 负责消息界面展示,包括多类型消息收发,消息引用/删除/撤回/转发、查询消息已读回执详情等功能。
+  - TUIConversation: 负责会话列表的展示和编辑,包括会话置顶、会话消息免打扰、会话删除等功能.
+  - TUIGroup: 负责群聊的创建以及群资料、群成员、群组权限、群公告、群禁言的管理。
+  - TUIContact: 负责联系人与群组展示,添加好友,移入黑名单,好友备注,信息展示等功能。
+- 同时,我们还提供了功能丰富的插件系统:
+  - TUICustomerService: 在线客服插件,支持灵活的路由排队、客服接待、智能机器人功能,配合功能丰富的管理端与数据分析能力,支持客服多终端办公,免费试用请点击 https://cloud.tencent.com/document/product/269/92648#ae4e3f5c-94db-4df3-8a49-65d23ce417b8 开通。
+  - TUICallKit: 音视频通话 UI 组件,支持两人或多人进行音视频通话,覆盖游戏社交、在线客服、视频客服、在线问诊、保险咨询等场景,免费试用请点击 https://cloud.tencent.com/document/product/269/79861#step1 开通。

+ 56 - 0
TUIKit/README.md

@@ -0,0 +1,56 @@
+## 关于 chat-uikit-uniapp
+
+chat-uikit-uniapp (vue2 / vue3)是基于腾讯云 Chat SDK 的一款 uniapp UI 组件库,它提供了一些通用的 UI 组件,包含会话、聊天、群组、关系链等功能。基于这些精心设计的 UI 组件,您可以快速构建优雅的、可靠的、可扩展的 Chat 应用。
+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
+- iOS
+- 微信小程序
+- H5
+
+## 含 UI 集成 TUILogin 使用说明
+
+``` javascript
+// 引入 TUILogin 模块
+import { TUILogin } from '@tencentcloud/tui-core';
+```
+初始化登录参数 options 配置说明:
+| 参数 | 类型 | 含义 |
+| --- | --- | --- |
+| SDKAppID | number | 云通信应用的 SDKAppID,必填 |
+| userID | string | 用户 ID,必填 |
+| userSig |string | 用户登录密钥,必填 |
+| TIMPush | any | 推送插件实例,uniapp 打包 app 时集成推送插件可用 |
+| pushConfig | object | 推送插件配置信息,uniapp 打包 app 时集成推送插件可用 |
+| useUploadPlugin | boolean | 是否使用上传插件, 默认 false |
+| proxyServer | string | WebSocket 服务器代理地址 |
+| fileUploadProxy | string | 图片、视频、文件上传代理地址 |
+| fileDownloadProxy | string | 图片、视频、文件下载代理地址 |
+| framework | string \| undefined | 使用的 UI 框架,可选值: vue2、vue3、undefined,必填 |
+
+``` javascript
+// 初始化登录
+TUILogin.login(options);
+```
+
+``` javascript
+// 登出
+TUILogin.logout();
+```
+
+``` javascript
+// 设置 Chat SDK 日志输出级别
+TUILogin.setLogLevel(0); // 0:普通日志级别 1:release 级别日志 2:告警级别 3:错误级别 4:无日志级别
+```
+
+``` javascript
+// 获取 Chat SDK 实例
+const { chat } = TUILogin.getContext();
+```
+
+## 【源码集成】[请参考 TUIKit 集成文档](https://cloud.tencent.com/document/product/269/64507)

+ 14 - 0
TUIKit/adapter-vue.ts

@@ -0,0 +1,14 @@
+let vueVersion: number;
+let framework = 'vue2';
+// #ifndef VUE3
+export * from '@vue/composition-api';
+vueVersion = 2;
+// #endif
+
+// #ifdef VUE3
+export * from 'vue';
+vueVersion = 3;
+framework = 'vue3';
+// #endif
+console.warn(`[adapter-vue]: vue version is ${vueVersion}`);
+export { vueVersion, framework };

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

@@ -0,0 +1,7 @@
+<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>

+ 12 - 0
TUIKit/assets/icon/add.svg

@@ -0,0 +1,12 @@
+<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <g opacity="0.80492">
+        <rect x="0.25" y="7.25" width="13.5" height="0.5" rx="0.25" stroke="#232832"
+            style="stroke:#232832;stroke:color(display-p3 0.1360 0.1574 0.1949);stroke-opacity:1;"
+            stroke-width="0.5" />
+        <rect x="6.75" y="14.25" width="13.5" height="0.5" rx="0.25"
+            transform="rotate(-90 6.75 14.25)" stroke="#232832"
+            style="stroke:#232832;stroke:color(display-p3 0.1360 0.1574 0.1949);stroke-opacity:1;"
+            stroke-width="0.5" />
+    </g>
+</svg>
+    

File diff suppressed because it is too large
+ 12 - 0
TUIKit/assets/icon/at.svg


File diff suppressed because it is too large
+ 6 - 0
TUIKit/assets/icon/audio.svg


File diff suppressed because it is too large
+ 16 - 0
TUIKit/assets/icon/back.svg


File diff suppressed because it is too large
+ 6 - 0
TUIKit/assets/icon/call-video.svg


File diff suppressed because it is too large
+ 6 - 0
TUIKit/assets/icon/call-voice.svg


BIN
TUIKit/assets/icon/call.png


BIN
TUIKit/assets/icon/camera-uni.png


+ 23 - 0
TUIKit/assets/icon/cancel.svg

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
+  <title>清除</title>
+  <g id="页面-2备份" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="创建群聊" transform="translate(-1243.000000, -413.000000)">
+      <g id="Group-1364" transform="translate(650.000000, 343.000000)">
+        <g id="Group-1363" transform="translate(290.000000, 46.000000)">
+          <g id="Group-1358" transform="translate(0.000000, 24.000000)">
+            <g id="清除" transform="translate(303.000000, 0.000000)">
+              <path d="M0,0 L16,0 L16,16 L0,16 L0,0 Z" id="矩形"></path>
+              <path d="M1,8 C1,11.8659999 4.13400006,15 8,15 C11.8659999,15 15,11.8659999 15,8 C15,4.13400006 11.8659999,1 8,1 L8,1 C4.13400006,1 1,4.13400006 1,8 Z" fill="#999999"></path>
+              <g id="编组" transform="translate(8.000000, 8.000000) rotate(-315.000000) translate(-8.000000, -8.000000) translate(4.000000, 4.000000)" fill="#FFFFFF">
+                <rect id="矩形备份" transform="translate(4.000000, 4.000000) rotate(-90.000000) translate(-4.000000, -4.000000) " x="1.8189894e-12" y="3.5" width="8" height="1" rx="0.5"></rect>
+                <rect id="矩形备份" transform="translate(4.000000, 4.000000) rotate(-360.000000) translate(-4.000000, -4.000000) " x="0" y="3.5" width="8" height="1" rx="0.5"></rect>
+              </g>
+              <rect id="矩形" x="0" y="0" width="16" height="16"></rect>
+            </g>
+          </g>
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

File diff suppressed because it is too large
+ 3 - 0
TUIKit/assets/icon/check-sm.svg


File diff suppressed because it is too large
+ 3 - 0
TUIKit/assets/icon/close-dark.svg


BIN
TUIKit/assets/icon/close-image.png


+ 7 - 0
TUIKit/assets/icon/convertText_en.svg

@@ -0,0 +1,7 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<mask id="path-1-inside-1_2075_534" fill="white">
+<rect y="6.10352e-05" width="16" height="16" rx="0.5"/>
+</mask>
+<rect y="6.10352e-05" width="16" height="16" rx="0.5" fill="white" stroke="#444444" stroke-width="4" mask="url(#path-1-inside-1_2075_534)"/>
+<path d="M7.276 3.43201H8.872L12.148 12H10.648L9.868 9.85201H6.28L5.5 12H4L7.276 3.43201ZM6.7 8.70001H9.448L8.104 4.94401H8.056L6.7 8.70001Z" fill="#444444"/>
+</svg>

File diff suppressed because it is too large
+ 8 - 0
TUIKit/assets/icon/convertText_zh.svg


+ 5 - 0
TUIKit/assets/icon/d-left-arrow.svg

@@ -0,0 +1,5 @@
+<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
+    <path fill="#444444"
+        style="fill:#444444;fill:color(display-p3 0.2667 0.2667 0.2667);fill-opacity:1;"
+        d="M529.408 149.376a29.12 29.12 0 0 1 41.728 0 30.592 30.592 0 0 1 0 42.688L259.264 511.936l311.872 319.936a30.592 30.592 0 0 1-.512 43.264 29.12 29.12 0 0 1-41.216-.512L197.76 534.272a32 32 0 0 1 0-44.672l331.648-340.224zm256 0a29.12 29.12 0 0 1 41.728 0 30.592 30.592 0 0 1 0 42.688L515.264 511.936l311.872 319.936a30.592 30.592 0 0 1-.512 43.264 29.12 29.12 0 0 1-41.216-.512L453.76 534.272a32 32 0 0 1 0-44.672l331.648-340.224z"></path>
+</svg>

+ 5 - 0
TUIKit/assets/icon/d-right-arrow.svg

@@ -0,0 +1,5 @@
+<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
+    <path fill="#444444"
+        style="fill:#444444;fill:color(display-p3 0.2667 0.2667 0.2667);fill-opacity:1;"
+        d="M452.864 149.312a29.12 29.12 0 0 1 41.728.064L826.24 489.664a32 32 0 0 1 0 44.672L494.592 874.624a29.12 29.12 0 0 1-41.728 0 30.592 30.592 0 0 1 0-42.752L764.736 512 452.864 192a30.592 30.592 0 0 1 0-42.688zm-256 0a29.12 29.12 0 0 1 41.728.064L570.24 489.664a32 32 0 0 1 0 44.672L238.592 874.624a29.12 29.12 0 0 1-41.728 0 30.592 30.592 0 0 1 0-42.752L508.736 512 196.864 192a30.592 30.592 0 0 1 0-42.688z"></path>
+</svg>

+ 25 - 0
TUIKit/assets/icon/del-icon.svg

@@ -0,0 +1,25 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <g id="&#231;&#188;&#150;&#231;&#187;&#132; 9&#229;&#164;&#135;&#228;&#187;&#189;">
+        <path id="&#231;&#155;&#180;&#231;&#186;&#191;" d="M2.15682 4.76268H14.0347"
+            stroke="#FF584C"
+            style="stroke:#FF584C;stroke:color(display-p3 1.0000 0.3451 0.2980);stroke-opacity:1;"
+            stroke-linecap="round" />
+        <path id="&#231;&#155;&#180;&#231;&#186;&#191;&#229;&#164;&#135;&#228;&#187;&#189;"
+            d="M5.23978 2.15307H11.1684" stroke="#FF584C"
+            style="stroke:#FF584C;stroke:color(display-p3 1.0000 0.3451 0.2980);stroke-opacity:1;"
+            stroke-linecap="round" />
+        <path id="&#231;&#155;&#180;&#231;&#186;&#191;&#229;&#164;&#135;&#228;&#187;&#189; 2"
+            d="M6.90014 7.32796V10.9714" stroke="#FF584C"
+            style="stroke:#FF584C;stroke:color(display-p3 1.0000 0.3451 0.2980);stroke-opacity:1;"
+            stroke-linecap="round" />
+        <path id="&#231;&#155;&#180;&#231;&#186;&#191;&#229;&#164;&#135;&#228;&#187;&#189; 3"
+            d="M9.58605 7.32796V10.9714" stroke="#FF584C"
+            style="stroke:#FF584C;stroke:color(display-p3 1.0000 0.3451 0.2980);stroke-opacity:1;"
+            stroke-linecap="round" />
+        <path id="&#231;&#159;&#169;&#229;&#189;&#162;"
+            d="M4.14331 4.8316H12.1596V12.9091C12.1596 13.5367 11.6508 14.0454 11.0232 14.0454H5.27968C4.65208 14.0454 4.14331 13.5367 4.14331 12.9091V4.8316Z"
+            stroke="#FF584C"
+            style="stroke:#FF584C;stroke:color(display-p3 1.0000 0.3451 0.2980);stroke-opacity:1;" />
+    </g>
+</svg>
+    

File diff suppressed because it is too large
+ 1 - 0
TUIKit/assets/icon/double-arrow.svg


+ 3 - 0
TUIKit/assets/icon/down-icon.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 5L7.21955 10L3 5" stroke="#979797"/>
+</svg>

BIN
TUIKit/assets/icon/downaload-image.png


File diff suppressed because it is too large
+ 6 - 0
TUIKit/assets/icon/download.svg


File diff suppressed because it is too large
+ 7 - 0
TUIKit/assets/icon/edit.svg


File diff suppressed because it is too large
+ 10 - 0
TUIKit/assets/icon/evaluate.svg


BIN
TUIKit/assets/icon/face-uni.png


BIN
TUIKit/assets/icon/face.png


BIN
TUIKit/assets/icon/files.png


File diff suppressed because it is too large
+ 6 - 0
TUIKit/assets/icon/forward-each.svg


File diff suppressed because it is too large
+ 8 - 0
TUIKit/assets/icon/forward-merge.svg


+ 7 - 0
TUIKit/assets/icon/icon-arrow-left.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg class="icon" width="32px" height="32.00px" viewBox="0 0 1024 1024" version="1.1"
+  xmlns="http://www.w3.org/2000/svg">
+  <path fill="#444444"
+    d="M384 512L731.733333 202.666667c17.066667-14.933333 19.2-42.666667 4.266667-59.733334-14.933333-17.066667-42.666667-19.2-59.733333-4.266666l-384 341.333333c-10.666667 8.533333-14.933333 19.2-14.933334 32s4.266667 23.466667 14.933334 32l384 341.333333c8.533333 6.4 19.2 10.666667 27.733333 10.666667 12.8 0 23.466667-4.266667 32-14.933333 14.933333-17.066667 14.933333-44.8-4.266667-59.733334L384 512z" />
+</svg>

File diff suppressed because it is too large
+ 36 - 0
TUIKit/assets/icon/icon-c2c.svg


File diff suppressed because it is too large
+ 6 - 0
TUIKit/assets/icon/icon-close.svg


BIN
TUIKit/assets/icon/image-uni.png


BIN
TUIKit/assets/icon/image.png


+ 3 - 0
TUIKit/assets/icon/input-close.svg

@@ -0,0 +1,3 @@
+<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 14C11.366 14 14.5 10.866 14.5 7C14.5 3.13401 11.366 0 7.5 0C3.63401 0 0.5 3.13401 0.5 7C0.5 10.866 3.63401 14 7.5 14ZM9.98528 3.47487L11.0459 4.53553L8.57107 7.01041L11.0459 9.48528L9.98528 10.5459L7.51041 8.07107L5.03553 10.5459L3.97487 9.48528L6.44975 7.01041L3.97487 4.53553L5.03553 3.47487L7.51041 5.94975L9.98528 3.47487Z" fill="#B2B2B2" style="fill:#B2B2B2;fill:color(display-p3 0.6980 0.6980 0.6980);fill-opacity:1;"/>
+</svg>

+ 6 - 0
TUIKit/assets/icon/left-arrow.svg

@@ -0,0 +1,6 @@
+<svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path
+        d="M2.1614 7.00004L7.64211 12.2881L6.5614 13.3308L1.0807 8.04275L0 7.00004L1.0807 5.95733L6.5614 0.669312L7.64211 1.71202L2.1614 7.00004Z"
+        fill="#444444"
+        style="fill:#444444;fill:color(display-p3 0.2667 0.2667 0.2667);fill-opacity:1;" />
+</svg>

BIN
TUIKit/assets/icon/loading.gif


BIN
TUIKit/assets/icon/loading.png


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

@@ -0,0 +1,6 @@
+<svg width="36" height="4" viewBox="0 0 36 4" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M19.499 0.5L36 0.5V3.5H19.499H16.499H0V0.5L16.499 0.5H19.499Z" fill="#BBBBBB"
+        style="fill:#BBBBBB;fill:color(display-p3 0.7333 0.7333 0.7333);fill-opacity:1;" />
+</svg>
+    

BIN
TUIKit/assets/icon/more-uni.png


BIN
TUIKit/assets/icon/more.png


File diff suppressed because it is too large
+ 1 - 0
TUIKit/assets/icon/msg-audio.svg


File diff suppressed because it is too large
+ 30 - 0
TUIKit/assets/icon/msg-copy.svg


+ 33 - 0
TUIKit/assets/icon/msg-del.svg

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40px" height="40px" viewBox="0 0 40 40" version="1.1">
+  <title>矩形</title>
+  <defs>
+    <path d="M467,0 C474.731986,-2.71135202e-14 481,6.2680135 481,14 L481,247 C481,254.731986 474.731986,261 467,261 L353.036,261 L340.862492,273.204941 C339.302377,274.769018 336.769719,274.77223 335.205642,273.212116 C335.203247,273.209727 335.200856,273.207335 335.198467,273.204941 L323.024,261 L14,261 C6.2680135,261 2.72325209e-15,254.731986 0,247 L0,14 C8.29461588e-16,6.2680135 6.2680135,3.19669972e-15 14,0 L467,0 Z" id="path-1"></path>
+    <filter x="-10.1%" y="-14.0%" width="120.2%" height="135.4%" filterUnits="objectBoundingBox" id="filter-2">
+      <feOffset dx="0" dy="10" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+      <feGaussianBlur stdDeviation="14.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+      <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.06 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+    </filter>
+  </defs>
+  <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="消息状态" transform="translate(-779.000000, -326.000000)">
+      <g id="编组-11" transform="translate(499.000000, 163.000000)">
+        <g id="形状结合">
+          <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+          <use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
+        </g>
+        <g id="编组-13备份" transform="translate(246.000000, 131.583780)">
+          <g id="编组-17" transform="translate(34.000000, 31.416220)">
+            <g id="编组-18" transform="translate(4.000000, 2.847939)">
+              <path d="M27,8.04349833 L27,34.26099 L5,34.26099 L5,8.04349833 L27,8.04349833 Z" id="路径-5" stroke="#444444" stroke-width="4"></path>
+              <rect id="矩形" fill="#444444" x="9.14285714" y="0" width="14" height="4.02899889"></rect>
+              <rect id="矩形" fill="#444444" x="0" y="6.04349833" width="32" height="4.02899889"></rect>
+              <path d="M14,15.1087458 L14,27.1957425 L10,27.1957425 L10,15.1087458 L14,15.1087458 Z M22,15.1087458 L22,27.1957425 L18,27.1957425 L18,15.1087458 L22,15.1087458 Z" id="形状结合" fill="#444444"></path>
+            </g>
+          </g>
+        </g>
+        <g id="编组-19" transform="translate(8.000000, 1.000000)"></g>
+      </g>
+    </g>
+  </g>
+</svg>

File diff suppressed because it is too large
+ 31 - 0
TUIKit/assets/icon/msg-forward.svg


+ 8 - 0
TUIKit/assets/icon/msg-quote.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 41" class="design-iconfont">
+  <g fill="none" fill-rule="evenodd">
+    <path d="M34,2 L34,28.6209709 L20.7153357,28.6209709 L17.9993455,31.4022694 L15.2847485,28.6219998 L2,28.6219998 L2,2 L34,2 Z" transform="translate(2 3.273066)" fill-rule="nonzero" stroke="#444" stroke-width="4"/>
+    <path fill="#444" d="M11 16.12483H15V20.15603749H11z" transform="translate(0 .24966)"/>
+    <path fill="#444" d="M18 16.12483H22V20.15603749H18z" transform="translate(0 .24966)"/>
+    <path fill="#444" d="M25 16.12483H29V20.15603749H25z" transform="translate(0 .24966)"/>
+  </g>
+</svg>

File diff suppressed because it is too large
+ 29 - 0
TUIKit/assets/icon/msg-revoke.svg


File diff suppressed because it is too large
+ 5 - 0
TUIKit/assets/icon/multiple-select.svg


+ 8 - 0
TUIKit/assets/icon/mute.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="design-iconfont">
+  <g fill="none" fill-rule="evenodd">
+    <path d="M4.13744611,6.61713753 L20.551,23 L0,23 L0,21 L3,21 L3,11 C3,9.40883362 3.41291862,7.91410954 4.13744611,6.61713753 Z M21.481,21 L24,21 L24,23 L23.485,23 L21.481,21 Z M12,2 C16.9705627,2 21,6.02943725 21,11 L21,20.52 L5.36684476,4.91705738 C7.01203082,3.12402075 9.37475086,2 12,2 Z" fill="#CCC" transform="translate(4 3)"/>
+    <path fill="#CCC" fill-rule="nonzero" d="M13 0L13 4 11 4 11 0z" transform="translate(4 3)"/>
+    <path stroke="#CCC" d="M8.5 24.5H15.5V25.5H8.5z" transform="translate(4 3)"/>
+    <path stroke="#CCC" stroke-width="2" transform="matrix(-1 0 0 1 31.089472 3)" d="M25.4499982 0.1546001L1.63947409 23.8855564"/>
+  </g>
+</svg>

+ 7 - 0
TUIKit/assets/icon/plus.svg

@@ -0,0 +1,7 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <path fill-rule="evenodd" clip-rule="evenodd"
+        d="M21.5 0L21.499 18.5H40V21.5H21.499L21.5 40H18.5L18.499 21.5H0V18.5H18.499L18.5 0H21.5Z"
+        fill="#BBBBBB"
+        style="fill:#BBBBBB;fill:color(display-p3 0.7333 0.7333 0.7333);fill-opacity:1;" />
+</svg>
+    

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

@@ -0,0 +1,6 @@
+<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>

+ 14 - 0
TUIKit/assets/icon/right-arrow.svg

@@ -0,0 +1,14 @@
+<svg width="9" height="14" viewBox="0 0 9 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <g clip-path="url(#clip0_2200_23553)">
+        <path
+            d="M6.48068 6.83073L0.999983 12.1188L2.08069 13.1615L7.56139 7.87344L8.64209 6.83073L7.56139 5.78802L2.08069 0.5L0.999983 1.54271L6.48068 6.83073Z"
+            fill="#444444"
+            style="fill:#444444;fill:color(display-p3 0.2667 0.2667 0.2667);fill-opacity:1;" />
+    </g>
+    <defs>
+        <clipPath id="clip0_2200_23553">
+            <rect width="8" height="13" fill="white" style="fill:white;fill:white;fill-opacity:1;"
+                transform="matrix(-1 0 0 1 8.64209 0.5)" />
+        </clipPath>
+    </defs>
+</svg>

+ 3 - 0
TUIKit/assets/icon/right-icon.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5 3L10 7.78045L5 12" stroke="#979797"/>
+</svg>

File diff suppressed because it is too large
+ 7 - 0
TUIKit/assets/icon/rotate-left.svg


File diff suppressed because it is too large
+ 7 - 0
TUIKit/assets/icon/rotate-right.svg


File diff suppressed because it is too large
+ 52 - 0
TUIKit/assets/icon/search-default.svg


+ 7 - 0
TUIKit/assets/icon/search-more.svg

@@ -0,0 +1,7 @@
+<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 4.5C0.5 2.29086 2.29086 0.5 4.5 0.5L20.5 0.5C22.7091 0.5 24.5 2.29086 24.5 4.5V20.5C24.5 22.7091 22.7091 24.5 20.5 24.5H4.5C2.29086 24.5 0.5 22.7091 0.5 20.5V4.5Z" fill="#F4F5F9" style="fill:#F4F5F9;fill:color(display-p3 0.9569 0.9608 0.9765);fill-opacity:1;"/>
+<g opacity="0.80492">
+<rect x="5.75" y="12.25" width="13.5" height="0.5" rx="0.25" stroke="#232832" style="stroke:#232832;stroke:color(display-p3 0.1360 0.1574 0.1949);stroke-opacity:1;" stroke-width="0.5"/>
+<rect x="12.25" y="19.25" width="13.5" height="0.5" rx="0.25" transform="rotate(-90 12.25 19.25)" stroke="#232832" style="stroke:#232832;stroke:color(display-p3 0.1360 0.1574 0.1949);stroke-opacity:1;" stroke-width="0.5"/>
+</g>
+</svg>

File diff suppressed because it is too large
+ 5 - 0
TUIKit/assets/icon/search.svg


+ 27 - 0
TUIKit/assets/icon/selected.svg

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
+  <title>编组 14</title>
+  <defs>
+    <filter x="-10.6%" y="-5.4%" width="121.2%" height="110.9%" filterUnits="objectBoundingBox" id="filter-1">
+      <feOffset dx="0" dy="7" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+      <feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+      <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+      <feMerge>
+        <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+        <feMergeNode in="SourceGraphic"></feMergeNode>
+      </feMerge>
+    </filter>
+  </defs>
+  <g id="new" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g id="自定义消息" transform="translate(-458.000000, -318.000000)">
+      <g id="编组-32" filter="url(#filter-1)" transform="translate(44.000000, 60.000000)">
+        <g id="编组-24" transform="translate(30.000000, 250.000000)">
+          <g id="编组-14" transform="translate(384.000000, 8.000000)">
+            <circle id="椭圆形" fill="#006EFF" fill-rule="nonzero" cx="8" cy="8" r="8"></circle>
+            <polyline id="路径-4" stroke="#FFFFFF" stroke-width="2" transform="translate(8.042641, 6.242641) rotate(-315.000000) translate(-8.042641, -6.242641) " points="6.04264069 10.2426407 10.0426407 10.2426407 10.0426407 2.24264069"></polyline>
+          </g>
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

+ 8 - 0
TUIKit/assets/icon/setting.svg

@@ -0,0 +1,8 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <g id="&#231;&#188;&#150;&#231;&#187;&#132; 14" opacity="0.802743">
+        <path id="&#229;&#189;&#162;&#231;&#138;&#182;&#231;&#187;&#147;&#229;&#144;&#136;"
+            fill-rule="evenodd" clip-rule="evenodd"
+            d="M6 10.5C6 11.3284 5.32843 12 4.5 12C3.67157 12 3 11.3284 3 10.5C3 9.67157 3.67157 9 4.5 9C5.32843 9 6 9.67157 6 10.5ZM12 10.5C12 11.3284 11.3284 12 10.5 12C9.67157 12 9 11.3284 9 10.5C9 9.67157 9.67157 9 10.5 9C11.3284 9 12 9.67157 12 10.5ZM16.5 12C17.3284 12 18 11.3284 18 10.5C18 9.67157 17.3284 9 16.5 9C15.6716 9 15 9.67157 15 10.5C15 11.3284 15.6716 12 16.5 12Z"
+            fill="#232832" />
+    </g>
+</svg>

BIN
TUIKit/assets/icon/star-light.png


BIN
TUIKit/assets/icon/star.png


+ 6 - 0
TUIKit/assets/icon/start-group.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
+  <circle r="2.61111" transform="matrix(1 0 0 -1 8.55545 3.88889)" stroke="#4C5059"></circle>
+  <circle r="2.61111" transform="matrix(1 0 0 -1 4.66678 3.88889)" fill="#F4F5F9" stroke="#4C5059"></circle>
+  <path d="M5.16675 11.7778C5.16675 9.84484 6.73375 8.27783 8.66675 8.27783H10.0001C11.9331 8.27783 13.5001 9.84484 13.5001 11.7778V12.7223H5.16675V11.7778Z" stroke="#4C5059"></path>
+  <path d="M0.5 11.7778C0.5 9.84484 2.067 8.27783 4 8.27783H5.33333C7.26633 8.27783 8.83333 9.84484 8.83333 11.7778V12.7223H0.5V11.7778Z" fill="#F4F5F9" stroke="#4C5059"></path>
+</svg>

+ 12 - 0
TUIKit/assets/icon/translate.svg

@@ -0,0 +1,12 @@
+<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<mask id="path-1-inside-1_313_82772" fill="white">
+<rect x="7" y="7.62939e-06" width="12" height="12" rx="0.5"/>
+</mask>
+<rect x="7" y="7.62939e-06" width="12" height="12" rx="0.5" fill="#444444" stroke="#CCCCCC" stroke-width="4" mask="url(#path-1-inside-1_313_82772)"/>
+<path d="M12.7426 3.61601H12.6745L12.6496 3.67939L10.9636 7.96339L10.9098 8.10001H11.0566H11.5846H11.6536L11.6781 8.03553L12.1096 6.90001H13.9397L14.3712 8.03553L14.3957 8.10001H14.4646H14.9986H15.1455L15.0917 7.96339L13.4057 3.67939L13.3808 3.61601H13.3126H12.7426ZM13.7079 6.28601H12.3421L13.03 4.49341L13.7079 6.28601Z" fill="#CCCCCC" stroke="#CCCCCC" stroke-width="0.2"/>
+<mask id="path-3-inside-2_313_82772" fill="white">
+<rect y="6.00001" width="12" height="12" rx="0.5"/>
+</mask>
+<rect y="6.00001" width="12" height="12" rx="0.5" fill="#444444" stroke="#CCCCCC" stroke-width="4" mask="url(#path-3-inside-2_313_82772)"/>
+<path d="M5.77801 8.97401H5.67801V9.07401V10.054H3.63H3.53V10.154V12.974V13.074H3.63H4.062H4.162V12.974V12.696H5.67801V14.642V14.742H5.77801H6.222H6.322V14.642V12.696H7.84401V12.974V13.074H7.94401H8.37601H8.47601V12.974V10.154V10.054H8.37601H6.322V9.07401V8.97401H6.222H5.77801ZM4.162 12.076V10.674H5.67801V12.076H4.162ZM6.322 12.076V10.674H7.84401V12.076H6.322Z" fill="#CCCCCC" stroke="#CCCCCC" stroke-width="0.2"/>
+</svg>

BIN
TUIKit/assets/icon/video-play.png


BIN
TUIKit/assets/icon/video-uni.png


BIN
TUIKit/assets/icon/video.png


+ 9 - 0
TUIKit/assets/icon/words.svg

@@ -0,0 +1,9 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <g opacity="0.795169">
+        <path
+            d="M9.60349 12.4231H9.35784L9.20772 12.6175L8.01519 14.1621L6.92003 12.6321L6.77045 12.4231H6.51345H3.25C2.2835 12.4231 1.5 11.6396 1.5 10.6731V3.25C1.5 2.2835 2.2835 1.5 3.25 1.5H12.75C13.7165 1.5 14.5 2.2835 14.5 3.25V8.80223V10.6731C14.5 11.6396 13.7165 12.4231 12.75 12.4231H9.60349Z"
+            stroke="#232832" />
+        <path d="M9.29061 3.74268L5.97266 6.33109L9.88719 7.13334L6.61672 9.74268" stroke="#232832"
+            stroke-linecap="round" stroke-linejoin="round" />
+    </g>
+</svg>

File diff suppressed because it is too large
+ 9 - 0
TUIKit/assets/icon/zoom-in.svg


File diff suppressed because it is too large
+ 9 - 0
TUIKit/assets/icon/zoom-out.svg


+ 59 - 0
TUIKit/assets/styles/common.scss

@@ -0,0 +1,59 @@
+body, div, ul, ol, dt, dd, li, dl, h1, h2, h3, h4, p {
+  margin:0;
+  padding:0;
+  font-style:normal;
+
+  /* font:12px/22px"\5B8B\4F53",Arial,Helvetica,sans-serif; */
+}
+
+ol, ul, li {
+  list-style:none;
+}
+
+img {
+  border:0;
+  vertical-align:middle;
+  pointer-events:none;
+}
+
+body {
+  color:#000;
+  background:#FFF;
+}
+
+.clear {
+  clear:both;
+  height:1px;
+  width:100%;
+  overflow:hidden;
+  margin-top:-1px;
+}
+
+a {
+  color:#000;
+  text-decoration:none;
+  cursor: pointer;
+}
+
+a:hover {
+  text-decoration:none;
+}
+
+input, textarea {
+  user-select: auto;
+}
+
+input:focus, input:active, textarea:focus, textarea:active {
+  outline: none;
+}
+
+.chat-aside {
+  position: absolute;
+  top: 50px;
+  right: 0;
+  box-sizing: border-box;
+  width: 360px !important;
+  border-radius: 8px 0 0 8px;
+  z-index: 9999;
+  max-height: calc(100% - 50px);
+}

+ 99 - 0
TUIKit/assets/styles/sample.scss

@@ -0,0 +1,99 @@
+/* stylelint-disable */
+.TUIKit {
+  display: flex;
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+  text-align: left;
+  .TUIKit-navbar {
+    background: #e8e8e9;
+    overflow: hidden;
+    .TUIKit-navbar-item {
+      padding: 10px;
+      color: #147aff;
+      font-weight: 500;
+      cursor: pointer;
+    }
+    .TUIKit-navbar-item-active {
+      background: #dddddd;
+    }
+  }
+  .TUIKit-main-container {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+    .TUIKit-main {
+      flex: 1;
+      display: flex;
+      .TUIKit-main-aside {
+        min-width: 285px;
+        flex: 0 0 24%;
+        border-right: 1px solid #f4f5f9;
+      }
+      .TUIKit-main-main {
+        flex: 1;
+        display: flex;
+        .chat{
+          flex: 1;
+        }
+        .chat-aside {
+          position: absolute;
+          top: 50px;
+          right: 0;
+          box-sizing: border-box;
+          max-width: 360px;
+          max-height: calc(100% - 50px);
+          border-radius: 8px 0 0 8px;
+          z-index: 9999;
+        }
+      }
+    }
+    .callkit-container {
+      position: fixed;
+      left: calc(50% - 25rem);
+      top: calc(50% - 18rem);
+      width: 50rem;
+      height: 36rem;
+    }
+    .callkit-container.miniMized {
+      left: auto;
+      right: 10px;
+      top: 70px;
+      background: transparent;
+    }
+  }
+}
+.TUIKit-h5 {
+  display: flex;
+  flex-direction: column-reverse;
+  .TUIKit-navbar {
+    display: flex;
+    flex-direction: row;
+    .TUIKit-navbar-item {
+      flex: 1;
+      text-align: center;
+      cursor: none;
+    }
+  }
+  .TUIKit-main-container {
+    flex: 1;
+    .TUIKit-main {
+      .TUIKit-main-aside {
+        flex: 1;
+      }
+      .TUIKit-main-main {
+        .chat-popup {
+          position: absolute;
+          max-width: 100%;
+          max-height: 100%;
+        }
+      }
+    }
+    .callkit-container {
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+    }
+  }
+}

+ 62 - 0
TUIKit/components/TUIChat/chat-header/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <view style="display: none;" />
+</template>
+
+<script setup lang="ts">
+import {
+  TUIStore,
+  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';
+
+const currentConversation = ref<IConversationModel>();
+const typingStatus = ref(false);
+
+const setChatHeaderContent = (content: string) => {
+  TUIGlobal?.setNavigationBarTitle({
+    title: content || '云通信 IM',
+  });
+};
+
+onMounted(() => {
+  TUIStore.watch(StoreName.CONV, {
+    currentConversation: onCurrentConversationUpdated,
+  });
+  TUIStore.watch(StoreName.CHAT, {
+    typingStatus: onTypingStatusUpdated,
+  });
+});
+
+onUnmounted(() => {
+  TUIStore.unwatch(StoreName.CONV, {
+    currentConversation: onCurrentConversationUpdated,
+  });
+  TUIStore.unwatch(StoreName.CHAT, {
+    typingStatus: onTypingStatusUpdated,
+  });
+});
+
+onLoad(() => {
+  setChatHeaderContent(currentConversation.value?.getShowName());
+});
+
+function onCurrentConversationUpdated(conversation: IConversationModel) {
+  currentConversation.value = conversation;
+  if (!typingStatus.value) {
+    setChatHeaderContent(currentConversation?.value?.getShowName());
+  }
+}
+
+function onTypingStatusUpdated(status: boolean) {
+  typingStatus.value = status;
+  if (typingStatus.value) {
+    setChatHeaderContent(TUITranslateService.t('TUIChat.对方正在输入'));
+  } else {
+    setChatHeaderContent(currentConversation.value?.getShowName());
+  }
+}
+</script>

+ 73 - 0
TUIKit/components/TUIChat/config.ts

@@ -0,0 +1,73 @@
+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,
+      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 {
+    if (!TUIChatConfig.instance) {
+      TUIChatConfig.instance = new TUIChatConfig();
+    }
+    return TUIChatConfig.instance;
+  }
+
+  setChatType(chatType: string) {
+    this.chatType = chatType;
+  }
+
+  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;

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

@@ -0,0 +1,15 @@
+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> = {};

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

@@ -0,0 +1,114 @@
+/**
+ * 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]),
+  ),
+};

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

@@ -0,0 +1,140 @@
+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,
+};

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

@@ -0,0 +1,66 @@
+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;

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

@@ -0,0 +1,66 @@
+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;

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

@@ -0,0 +1,35 @@
+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();
+};

+ 159 - 0
TUIKit/components/TUIChat/forward/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <Overlay
+    :visible="isShowForwardPanel"
+    :useMask="false"
+  >
+    <Transfer
+      :title="TUITranslateService.t('TUIChat.转发')"
+      :isSearch="false"
+      :isCustomItem="false"
+      :list="customConversationList"
+      :isHiddenBackIcon="isUniFrameWork"
+      @cancel="closeForwardPanel"
+      @submit="finishSelected"
+    />
+  </Overlay>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref } from '../../../adapter-vue';
+import TUIChatEngine, {
+  TUIStore,
+  StoreName,
+  TUIChatService,
+  TUITranslateService,
+} 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;
+
+const isShowForwardPanel = ref(false);
+const customConversationList = ref();
+
+onMounted(() => {
+  TUIStore.watch(StoreName.CUSTOM, {
+    singleForwardMessageID: onSingleForwardMessageIDUpdated,
+    multipleForwardMessageID: onMultipleForwardMessageIDUpdated,
+  });
+});
+
+onUnmounted(() => {
+  TUIStore.unwatch(StoreName.CUSTOM, {
+    singleForwardMessageID: onSingleForwardMessageIDUpdated,
+    multipleForwardMessageID: onMultipleForwardMessageIDUpdated,
+  });
+
+  // tuistore data must be cleared when closing the forward panel
+  clearStoreData();
+});
+
+function onSingleForwardMessageIDUpdated(messageID: string | undefined) {
+  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();
+  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,
+    };
+    return TUIChatService.sendForwardMessage(
+      [conversation],
+      unsentMessageQueue,
+      {
+        needMerge: isMergeForward,
+        offlinePushInfo: OfflinePushInfoManager.create(offlinePushInfoCreateParams),
+        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;
+      }
+    }
+  });
+  closeForwardPanel();
+  emits('toggleMultipleSelectMode', false);
+}
+
+function getTransforRenderDataList() {
+  const conversationList = TUIStore.getData(StoreName.CONV, 'conversationList');
+  customConversationList.value = conversationList.map((conversation) => {
+    return {
+      // To achieve reusability of Transfer, userID is used here instead of ConversationID
+      userID: conversation.conversationID,
+      nick: conversation.getShowName(),
+      avatar: conversation.getAvatar(),
+    };
+  });
+}
+</script>

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

@@ -0,0 +1,6 @@
+import TUIChat from './index.vue';
+import Server from './server';
+
+new Server();
+
+export default TUIChat;

+ 298 - 0
TUIKit/components/TUIChat/index.vue

@@ -0,0 +1,298 @@
+<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>

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

@@ -0,0 +1,297 @@
+<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>

+ 180 - 0
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue

@@ -0,0 +1,180 @@
+<template>
+  <div
+    ref="emojiPickerDialog"
+    :class="{
+      'emoji-picker': true,
+      'emoji-picker-h5': !isPC
+    }"
+  >
+    <ul
+      ref="emojiPickerListRef"
+      :class="['emoji-picker-list', !isPC && 'emoji-picker-h5-list']"
+    >
+      <li
+        v-for="(childrenItem, childrenIndex) in currentEmojiList"
+        :key="childrenIndex"
+        class="emoji-picker-list-item"
+        @click="select(childrenItem, childrenIndex)"
+      >
+        <img
+          v-if="currentTabItem.type === EMOJI_TYPE.BASIC"
+          class="emoji"
+          :src="currentTabItem.url + BASIC_EMOJI_URL_MAPPING[childrenItem]"
+        >
+        <img
+          v-else-if="currentTabItem.type === EMOJI_TYPE.BIG"
+          class="emoji-big"
+          :src="currentTabItem.url + childrenItem + '@2x.png'"
+        >
+        <img
+          v-else
+          class="emoji-custom emoji-big"
+          :src="currentTabItem.url + childrenItem"
+        >
+      </li>
+    </ul>
+    <ul class="emoji-picker-tab">
+      <li
+        v-for="(item, index) in list"
+        :key="index"
+        class="emoji-picker-tab-item"
+        @click="toggleEmojiTab(index)"
+      >
+        <Icon
+          v-if="item.type === EMOJI_TYPE.BASIC"
+          class="icon"
+          :file="faceIcon"
+        />
+        <img
+          v-else-if="item.type === EMOJI_TYPE.BIG"
+          class="icon-big"
+          :src="item.url + item.list[0] + '@2x.png'"
+        >
+        <img
+          v-else
+          class="icon-custom icon-big"
+          :src="item.url + item.list[0]"
+        >
+      </li>
+      <li
+        v-if="isUniFrameWork"
+        class="send-btn"
+        @click="sendMessage"
+      >
+        发送
+      </li>
+    </ul>
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted, onUnmounted } from '../../../../adapter-vue';
+import {
+  TUIChatService,
+  TUIStore,
+  StoreName,
+  IConversationModel,
+  SendMessageParams,
+} from '@tencentcloud/chat-uikit-engine';
+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 { isEnabledMessageReadReceiptGlobal } from '../../utils/utils';
+import { EMOJI_GROUP_LIST, BASIC_EMOJI_URL_MAPPING, convertKeyToEmojiName } from '../../emoji-config';
+import TUIChatConfig from '../../config';
+
+const emits = defineEmits(['insertEmoji', 'onClose', 'sendMessage']);
+const currentTabIndex = ref<number>(0);
+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, {
+    currentConversation: onCurrentConversationUpdate,
+  });
+});
+
+onUnmounted(() => {
+  TUIStore.unwatch(StoreName.CONV, {
+    currentConversation: onCurrentConversationUpdate,
+  });
+});
+
+const toggleEmojiTab = (index: number) => {
+  currentTabIndex.value = index;
+  currentTabItem.value = list?.value[index];
+  currentEmojiList.value = list?.value[index]?.list;
+  // web & h5 side scroll to top
+  if (!isUniFrameWork) {
+    emojiPickerListRef?.value && (emojiPickerListRef.value.scrollTop = 0);
+  }
+};
+
+const select = (item: any, index: number) => {
+  const options: any = {
+    emoji: { key: item, name: convertKeyToEmojiName(item) },
+    type: currentTabItem?.value?.type,
+  };
+  switch (currentTabItem?.value?.type) {
+    case EMOJI_TYPE.BASIC:
+      options.url = currentTabItem?.value?.url + BASIC_EMOJI_URL_MAPPING[item];
+      if (isUniFrameWork) {
+        uni.$emit('insert-emoji', options);
+      } else {
+        emits('insertEmoji', options);
+      }
+      break;
+    case EMOJI_TYPE.BIG:
+      sendFaceMessage(index, currentTabItem.value);
+      break;
+    case EMOJI_TYPE.CUSTOM:
+      sendFaceMessage(index, currentTabItem.value);
+      break;
+    default:
+      break;
+  }
+  isPC && emits('onClose');
+};
+
+const sendFaceMessage = (index: number, listItem: IEmojiGroup) => {
+  const options = {
+    to:
+      currentConversation?.value?.groupProfile?.groupID
+      || currentConversation?.value?.userProfile?.userID,
+    conversationType: currentConversation?.value?.type,
+    payload: {
+      index: listItem.emojiGroupID,
+      data: listItem.list[index],
+    },
+    needReadReceipt: isEnabledMessageReadReceiptGlobal(),
+  } as SendMessageParams;
+  TUIChatService.sendFaceMessage(options);
+};
+
+function sendMessage() {
+  uni.$emit('send-message-in-emoji-picker');
+}
+
+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>

+ 2 - 0
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/index.ts

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

+ 78 - 0
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/index.vue

@@ -0,0 +1,78 @@
+<template>
+  <ToolbarItemContainer
+    ref="container"
+    :iconFile="faceIcon"
+    title="表情"
+    @onDialogShow="onDialogShow"
+    @onDialogClose="onDialogClose"
+  >
+    <EmojiPickerDialog
+      @insertEmoji="insertEmoji"
+      @sendMessage="sendMessage"
+      @onClose="onClose"
+    />
+  </ToolbarItemContainer>
+</template>
+<script lang="ts" setup>
+import {
+  TUIStore,
+  StoreName,
+  IConversationModel,
+} from '@tencentcloud/chat-uikit-engine';
+import { ref } from '../../../../adapter-vue';
+import faceIcon from '../../../../assets/icon/face.png';
+import EmojiPickerDialog from './emoji-picker-dialog.vue';
+import ToolbarItemContainer from '../toolbar-item-container/index.vue';
+import { isH5 } from '../../../../utils/env';
+import { ToolbarDisplayType } from '../../../../interface';
+
+interface IEmits {
+  (e: 'sendMessage'): void;
+  (e: 'toggleComponent'): void;
+  (e: 'insertEmoji', emoji: any): void;
+  (e: 'dialogShowInH5', dialogRef: HTMLElement): void;
+  (e: 'dialogCloseInH5', dialogRef: HTMLElement): void;
+  (e: 'changeToolbarDisplayType', type: ToolbarDisplayType): void;
+}
+
+const emits = defineEmits<IEmits>();
+const currentConversation = ref();
+const container = ref<InstanceType<typeof ToolbarItemContainer>>();
+
+TUIStore.watch(StoreName.CONV, {
+  currentConversation: (conversation: IConversationModel) => {
+    currentConversation.value = conversation;
+  },
+});
+
+const onDialogShow = (dialogRef: any) => {
+  if (!isH5) {
+    return;
+  }
+  emits('changeToolbarDisplayType', 'emojiPicker');
+  emits('dialogShowInH5', dialogRef.value);
+};
+
+const onDialogClose = (dialogRef: any) => {
+  if (!isH5) {
+    return;
+  }
+  emits('changeToolbarDisplayType', 'none');
+  emits('dialogCloseInH5', dialogRef.value);
+};
+
+const insertEmoji = (emojiObj) => {
+  emits('insertEmoji', emojiObj);
+};
+const sendMessage = () => {
+  emits('sendMessage');
+};
+const onClose = () => {
+  container.value?.toggleDialogDisplay(false);
+};
+
+defineExpose({
+  closeEmojiPicker: onClose,
+});
+</script>
+<style lang="scss" scoped src="./style/index.scss"></style>

+ 25 - 0
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/h5.scss

@@ -0,0 +1,25 @@
+.emoji-picker-h5 {
+  width: 100%;
+
+  &-list {
+    justify-content: space-between;
+  }
+
+  &-list::after {
+    content: "";
+    display: block;
+    flex: 1 1 auto;
+  }
+
+  .send-btn {
+    width: 50px;
+    height: 30px;
+    background-color: #55C06A;
+    position: absolute;
+    right: 10px;
+    font-size: 16px;
+    color: #fff;
+    text-align: center;
+    line-height: 30px;
+  }
+}

+ 4 - 0
TUIKit/components/TUIChat/message-input-toolbar/emoji-picker/style/index.scss

@@ -0,0 +1,4 @@
+@import "../../../../../assets/styles/common";
+@import "./web";
+@import "./h5";
+

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

@@ -0,0 +1,55 @@
+.emoji-picker {
+  width: 405px;
+  height: 300px;
+  display: flex;
+  flex-direction: column;
+
+  &-list {
+    flex: 1;
+    display: flex;
+    flex-wrap: wrap;
+    overflow-y: auto;
+    margin: 2px;
+
+    &::-webkit-scrollbar {
+      display: none;
+    }
+
+    &-item {
+      cursor: pointer;
+      padding: 5px;
+
+      .emoji {
+        width: 30px;
+        height: 30px;
+      }
+
+      .emoji-big {
+        width: 70px;
+        height: 70px;
+      }
+    }
+  }
+
+  &-tab {
+    display: flex;
+    align-items: center;
+
+    &-item {
+      padding: 0 10px;
+      cursor: pointer;
+
+      .icon {
+        margin: 10px;
+        width: 20px;
+        height: 20px;
+
+        &-big {
+          margin: 2px 0;
+          width: 30px;
+          height: 30px;
+        }
+      }
+    }
+  }
+}

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

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

+ 208 - 0
TUIKit/components/TUIChat/message-input-toolbar/evaluate/index.vue

@@ -0,0 +1,208 @@
+<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-header', !isPC && 'evaluate-h5-header']">
+        <div
+          :class="[
+            'evaluate-header-content',
+            !isPC && 'evaluate-h5-header-content',
+          ]"
+        >
+          {{ TUITranslateService.t("Evaluate.请对本次服务进行评价") }}
+        </div>
+        <div
+          v-if="!isPC"
+          :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',
+          ]"
+        >
+          <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>
+        <textarea
+          v-model="comment"
+          :class="[
+            'evaluate-content-text',
+            !isPC && 'evaluate-h5-content-text',
+          ]"
+        />
+        <div
+          :class="[
+            'evaluate-content-button',
+            !isPC && 'evaluate-h5-content-button',
+          ]"
+        >
+          <button
+            :class="['btn', isEvaluateValid ? 'btn-valid' : 'btn-invalid']"
+            @click="submitEvaluate"
+          >
+            {{ TUITranslateService.t("Evaluate.提交评价") }}
+          </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, {
+  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';
+
+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 = () => {
+  // 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);
+  // close dialog after submit evaluate
+  container?.value?.toggleDialogDisplay(false);
+};
+</script>
+<style scoped lang="scss" src="./style/index.scss"></style>

+ 57 - 0
TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/color.scss

@@ -0,0 +1,57 @@
+.evaluate {
+  background: #fff;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+
+  &-header {
+    &-content {
+      font-weight: 500;
+      color: #1c1c1c;
+    }
+  }
+
+  &-adv {
+    font-weight: 500;
+    color: #999;
+
+    a {
+      color: #006eff;
+    }
+  }
+
+  &-content {
+    &-text {
+      background: #f8f8f8;
+      border: 1px solid #ececec;
+    }
+
+    &-list {
+      &-item {
+        font-weight: 400;
+        color: #50545c;
+      }
+    }
+  }
+
+  &-H5 {
+    &-main {
+      background: rgba(0, 0, 0, 0.5);
+
+      .evaluate-main-content {
+        background: #fff;
+
+        p {
+          a {
+            color: #3370ff;
+          }
+        }
+
+        .close {
+          font-family: PingFangSC-Regular;
+          font-weight: 400;
+          color: #3370ff;
+          letter-spacing: 0;
+        }
+      }
+    }
+  }
+}

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

@@ -0,0 +1,63 @@
+.evaluate-h5 {
+  position: static;
+  width: 100%;
+  height: fit-content;
+  border-radius: 0;
+  background: #fff;
+  padding: 23px !important;
+  box-sizing: border-box;
+
+  &-header {
+    display: flex;
+    justify-content: space-between;
+
+    &-content {
+      font-size: 18px;
+    }
+
+    &-close {
+      font-size: 18px;
+      line-height: 27px;
+      font-weight: 400;
+      color: #3370ff;
+    }
+  }
+
+  &-content {
+    order: 1;
+
+    &-list {
+      &-item {
+        width: 40px;
+        height: 24px;
+        text-align: center;
+        cursor: auto;
+        font-size: 12px;
+      }
+    }
+
+    &-text {
+      font-size: 16px;
+      width: 100%;
+    }
+
+    &-button {
+      width: 100%;
+      display: flex;
+
+      .btn {
+        flex: 1;
+        padding: 14px 0;
+        font-size: 18px;
+        cursor: auto;
+      }
+    }
+  }
+
+  &-adv {
+    font-size: 14px;
+    font-weight: normal;
+    text-align: left;
+    color: #000;
+  }
+}

+ 0 - 0
TUIKit/components/TUIChat/message-input-toolbar/evaluate/style/index.scss


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