index.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. <template>
  2. <div :class="['message-input', !isPC && 'message-input-h5']">
  3. <div class="audio-main-content-line">
  4. <MessageInputAudio
  5. v-if="(isWeChat || isApp) && isRenderVoice"
  6. :class="{
  7. 'message-input-wx-audio-open': displayType === 'audio',
  8. }"
  9. :isEnableAudio="displayType === 'audio'"
  10. @changeDisplayType="changeDisplayType"
  11. />
  12. <MessageInputEditor
  13. v-show="displayType === 'editor'"
  14. ref="editor"
  15. class="message-input-editor"
  16. :placeholder="props.placeholder"
  17. :isMuted="props.isMuted"
  18. :muteText="props.muteText"
  19. :enableInput="props.enableInput"
  20. :enableAt="props.enableAt"
  21. :enableTyping="props.enableTyping"
  22. :isGroup="isGroup"
  23. @onTyping="onTyping"
  24. @onAt="onAt"
  25. @onFocus="onFocus"
  26. />
  27. <MessageInputAt
  28. v-if="props.enableAt"
  29. ref="messageInputAtRef"
  30. @insertAt="insertAt"
  31. @onAtListOpen="onAtListOpen"
  32. />
  33. <Icon
  34. v-if="isRenderEmojiPicker"
  35. class="icon icon-face"
  36. :file="faceIcon"
  37. :size="'23px'"
  38. :hotAreaSize="'3px'"
  39. @onClick="changeToolbarDisplayType('emojiPicker')"
  40. />
  41. <Icon
  42. v-if="isRenderMore"
  43. class="icon icon-more"
  44. :file="moreIcon"
  45. :size="'23px'"
  46. :hotAreaSize="'3px'"
  47. @onClick="changeToolbarDisplayType('tools')"
  48. />
  49. </div>
  50. <div>
  51. <MessageQuote
  52. :style="{minWidth: 0}"
  53. :displayType="displayType"
  54. />
  55. </div>
  56. </div>
  57. </template>
  58. <script setup lang="ts">
  59. import TUIChatEngine, {
  60. TUIStore,
  61. StoreName,
  62. IMessageModel,
  63. IConversationModel,
  64. } from '@tencentcloud/chat-uikit-engine';
  65. import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
  66. import MessageInputEditor from './message-input-editor.vue';
  67. import MessageInputAt from './message-input-at/index.vue';
  68. import MessageInputAudio from './message-input-audio.vue';
  69. import MessageQuote from './message-input-quote/index.vue';
  70. import Icon from '../../common/Icon.vue';
  71. import faceIcon from '../../../assets/icon/face-uni.png';
  72. import moreIcon from '../../../assets/icon/more-uni.png';
  73. import { isPC, isH5, isWeChat, isApp } from '../../../utils/env';
  74. import { sendTyping } from '../utils/sendMessage';
  75. import { ToolbarDisplayType, InputDisplayType } from '../../../interface';
  76. import TUIChatConfig from '../config';
  77. interface IProps {
  78. placeholder: string;
  79. isMuted?: boolean;
  80. muteText?: string;
  81. enableInput?: boolean;
  82. enableAt?: boolean;
  83. enableTyping?: boolean;
  84. replyOrReference?: Record<string, any>;
  85. inputToolbarDisplayType: ToolbarDisplayType;
  86. }
  87. interface IEmits {
  88. (e: 'changeToolbarDisplayType', displayType: ToolbarDisplayType): void;
  89. }
  90. const emits = defineEmits<IEmits>();
  91. const props = withDefaults(defineProps<IProps>(), {
  92. placeholder: 'this is placeholder',
  93. replyOrReference: () => ({}),
  94. isMuted: true,
  95. muteText: '',
  96. enableInput: true,
  97. enableAt: true,
  98. enableTyping: true,
  99. inputToolbarDisplayType: 'none',
  100. });
  101. const editor = ref();
  102. const messageInputAtRef = ref();
  103. const currentConversation = ref<IConversationModel>();
  104. const isGroup = ref<boolean>(false);
  105. const displayType = ref<InputDisplayType>('editor');
  106. const featureConfig = TUIChatConfig.getFeatureConfig();
  107. const isRenderVoice = ref<boolean>(featureConfig.InputVoice);
  108. const isRenderEmojiPicker = ref<boolean>(featureConfig.InputEmoji || featureConfig.InputStickers);
  109. const isRenderMore = ref<boolean>(featureConfig.InputImage || featureConfig.InputVideo || featureConfig.InputEvaluation || featureConfig.InputQuickReplies);
  110. onMounted(() => {
  111. TUIStore.watch(StoreName.CONV, {
  112. currentConversation: onCurrentConversationUpdated,
  113. });
  114. TUIStore.watch(StoreName.CHAT, {
  115. quoteMessage: onQuoteMessageUpdated,
  116. });
  117. });
  118. onUnmounted(() => {
  119. TUIStore.unwatch(StoreName.CONV, {
  120. currentConversation: onCurrentConversationUpdated,
  121. });
  122. TUIStore.unwatch(StoreName.CHAT, {
  123. quoteMessage: onQuoteMessageUpdated,
  124. });
  125. });
  126. watch(() => props.inputToolbarDisplayType, (newVal: ToolbarDisplayType) => {
  127. if (newVal !== 'none') {
  128. changeDisplayType('editor');
  129. }
  130. });
  131. function changeDisplayType(display: InputDisplayType) {
  132. displayType.value = display;
  133. if (display === 'audio') {
  134. emits('changeToolbarDisplayType', 'none');
  135. }
  136. }
  137. function changeToolbarDisplayType(displayType: ToolbarDisplayType) {
  138. emits('changeToolbarDisplayType', displayType);
  139. }
  140. const onTyping = (inputContentEmpty: boolean, inputBlur: boolean) => {
  141. sendTyping(inputContentEmpty, inputBlur);
  142. };
  143. const onAt = (show: boolean) => {
  144. messageInputAtRef?.value?.toggleAtList(show);
  145. };
  146. const onFocus = () => {
  147. if (isH5) {
  148. emits('changeToolbarDisplayType', 'none');
  149. }
  150. };
  151. const insertEmoji = (emoji: any) => {
  152. editor?.value?.addEmoji && editor?.value?.addEmoji(emoji);
  153. };
  154. const insertAt = (atInfo: any) => {
  155. editor?.value?.insertAt && editor?.value?.insertAt(atInfo);
  156. };
  157. const onAtListOpen = () => {
  158. editor?.value?.blur && editor?.value?.blur();
  159. };
  160. const reEdit = (content: any) => {
  161. editor?.value?.resetEditor();
  162. editor?.value?.setEditorContent(content);
  163. };
  164. function onCurrentConversationUpdated(conversation: IConversationModel) {
  165. currentConversation.value = conversation;
  166. isGroup.value = currentConversation.value?.type === TUIChatEngine.TYPES.CONV_GROUP;
  167. }
  168. function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
  169. // switch text input mode when there is a quote message
  170. if (options?.message && options?.type === 'quote') {
  171. changeDisplayType('editor');
  172. }
  173. }
  174. defineExpose({
  175. insertEmoji,
  176. reEdit,
  177. });
  178. </script>
  179. <style scoped lang="scss">
  180. @import "../../../assets/styles/common";
  181. :not(not) {
  182. display: flex;
  183. flex-direction: column;
  184. min-width: 0;
  185. box-sizing: border-box;
  186. }
  187. .message-input {
  188. position: relative;
  189. display: flex;
  190. flex-direction: column;
  191. border: none;
  192. overflow: hidden;
  193. background: #ebf0f6;
  194. &-h5 {
  195. padding: 10px 10px 15px;
  196. }
  197. &-editor {
  198. flex: 1;
  199. display: flex;
  200. }
  201. .icon {
  202. margin-left: 3px;
  203. }
  204. &-wx-audio-open {
  205. flex: 1;
  206. }
  207. }
  208. .audio-main-content-line {
  209. display: flex;
  210. flex-direction: row;
  211. align-items: center;
  212. }
  213. </style>