message-input-editor.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <template>
  2. <div
  3. :class="{
  4. 'message-input-container': true,
  5. 'message-input-container-h5': !isPC,
  6. }"
  7. >
  8. <div v-if="props.isMuted" class="message-input-mute">
  9. {{ props.muteText }}
  10. </div>
  11. <input
  12. id="editor"
  13. ref="inputRef"
  14. v-model="inputText"
  15. :adjust-position="true"
  16. cursor-spacing="20"
  17. confirm-type="send"
  18. :confirm-hold="true"
  19. maxlength="140"
  20. type="text"
  21. placeholder-class="input-placeholder"
  22. class="message-input-area"
  23. :placeholder="props.placeholder"
  24. auto-blur
  25. @confirm="handleSendMessage"
  26. @input="onInput"
  27. @blur="onBlur"
  28. @focus="onFocus"
  29. />
  30. </div>
  31. </template>
  32. <script lang="ts" setup>
  33. import config from "@/request/config";
  34. import { ref, watch, onMounted, onUnmounted } from "../../../adapter-vue";
  35. import {
  36. TUIStore,
  37. StoreName,
  38. IConversationModel,
  39. } from "@tencentcloud/chat-uikit-engine";
  40. import { TUIGlobal } from "@tencentcloud/universal-api";
  41. import { transformEmojiValueToKey } from "../utils/emojiList";
  42. import { isPC } from "../../../utils/env";
  43. import { sendMessages } from "../utils/sendMessage";
  44. import { ISendMessagePayload } from "../../../interface";
  45. const props = defineProps({
  46. placeholder: {
  47. type: String,
  48. default: "this is placeholder",
  49. },
  50. replayOrReferenceMessage: {
  51. type: Object,
  52. default: () => ({}),
  53. required: false,
  54. },
  55. isMuted: {
  56. type: Boolean,
  57. default: true,
  58. },
  59. muteText: {
  60. type: String,
  61. default: "",
  62. },
  63. enableInput: {
  64. type: Boolean,
  65. default: true,
  66. },
  67. enableAt: {
  68. type: Boolean,
  69. default: true,
  70. },
  71. enableTyping: {
  72. type: Boolean,
  73. default: true,
  74. },
  75. isGroup: {
  76. type: Boolean,
  77. default: false,
  78. },
  79. });
  80. const emits = defineEmits(["onTyping", "onFocus", "onAt"]);
  81. const inputText = ref("");
  82. const inputRef = ref();
  83. const inputBlur = ref(true);
  84. const inputContentEmpty = ref(true);
  85. const allInsertedAtInfo = new Map();
  86. const currentConversation = ref<IConversationModel>();
  87. onMounted(() => {
  88. TUIStore.watch(StoreName.CONV, {
  89. currentConversation: onCurrentConversationUpdated,
  90. });
  91. uni.$on("insert-emoji", (data) => {
  92. inputText.value += data?.emoji?.name;
  93. });
  94. uni.$on("send-message-in-emoji-picker", () => {
  95. handleSendMessage();
  96. });
  97. });
  98. onUnmounted(() => {
  99. uni.$off("insertEmoji");
  100. uni.$off("send-message-in-emoji-picker");
  101. TUIStore.unwatch(StoreName.CONV, {
  102. currentConversation: onCurrentConversationUpdated,
  103. });
  104. });
  105. const handleSendMessage = () => {
  106. const messageList = getEditorContent();
  107. resetEditor();
  108. sendMessages(messageList as any, currentConversation.value!); // im官方发送消息通信方法
  109. };
  110. const insertAt = (atInfo: any) => {
  111. if (!allInsertedAtInfo?.has(atInfo?.id)) {
  112. allInsertedAtInfo?.set(atInfo?.id, atInfo?.label);
  113. }
  114. inputText.value += atInfo?.label;
  115. };
  116. const getEditorContent = () => {
  117. let text = inputText.value;
  118. text = transformEmojiValueToKey(text);
  119. const atUserList: string[] = [];
  120. allInsertedAtInfo?.forEach((value: string, key: string) => {
  121. if (text?.includes("@" + value)) {
  122. atUserList.push(key);
  123. }
  124. });
  125. const payload: ISendMessagePayload = {
  126. text,
  127. };
  128. if (atUserList?.length) {
  129. payload.atUserList = atUserList;
  130. }
  131. return [
  132. {
  133. type: "text",
  134. payload,
  135. },
  136. ];
  137. };
  138. const resetEditor = () => {
  139. inputText.value = "";
  140. inputContentEmpty.value = true;
  141. allInsertedAtInfo?.clear();
  142. };
  143. const setEditorContent = (content: any) => {
  144. inputText.value = content;
  145. };
  146. const onBlur = () => {
  147. inputBlur.value = true;
  148. };
  149. const onFocus = (e: any) => {
  150. inputBlur.value = false;
  151. emits("onFocus", e?.detail?.height);
  152. };
  153. const isEditorContentEmpty = () => {
  154. inputContentEmpty.value = inputText?.value?.length ? false : true;
  155. };
  156. const onInput = (e: any) => {
  157. // uniapp 识别 @ 消息
  158. const text = e?.detail?.value;
  159. isEditorContentEmpty();
  160. if (props.isGroup && (text.endsWith("@") || text.endsWith("@\n"))) {
  161. TUIGlobal?.hideKeyboard();
  162. emits("onAt", true);
  163. }
  164. };
  165. watch(
  166. () => [inputContentEmpty.value, inputBlur.value],
  167. (newVal: any, oldVal: any) => {
  168. if (newVal !== oldVal) {
  169. emits("onTyping", inputContentEmpty.value, inputBlur.value);
  170. }
  171. },
  172. {
  173. immediate: true,
  174. deep: true,
  175. }
  176. );
  177. function onCurrentConversationUpdated(conversation: IConversationModel) {
  178. currentConversation.value = conversation;
  179. }
  180. defineExpose({
  181. insertAt,
  182. resetEditor,
  183. setEditorContent,
  184. getEditorContent,
  185. });
  186. </script>
  187. <style lang="scss" scoped>
  188. @import "../../../assets/styles/common";
  189. .message-input-container {
  190. display: flex;
  191. flex-direction: column;
  192. flex: 1;
  193. height: calc(100% - 13px);
  194. width: calc(100% - 20px);
  195. padding: 3px 10px 10px;
  196. overflow: hidden;
  197. &-h5 {
  198. flex: 1;
  199. height: auto;
  200. background: #fff;
  201. border-radius: 9.4px;
  202. padding: 7px 0 7px 10px;
  203. font-size: 16px !important;
  204. max-height: 86px;
  205. }
  206. .message-input-mute {
  207. flex: 1;
  208. display: flex;
  209. color: #999;
  210. font-size: 14px;
  211. justify-content: center;
  212. align-items: center;
  213. }
  214. .message-input-area {
  215. flex: 1;
  216. display: flex;
  217. overflow-y: scroll;
  218. min-height: 20px;
  219. }
  220. }
  221. </style>