message-input-editor.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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 { TUIStore, StoreName, IConversationModel } from "@tencentcloud/chat-uikit-engine";
  36. import { TUIGlobal } from "@tencentcloud/universal-api";
  37. import { transformEmojiValueToKey } from "../utils/emojiList";
  38. import { isPC } from "../../../utils/env";
  39. import { sendMessages } from "../utils/sendMessage";
  40. import { ISendMessagePayload } from "../../../interface";
  41. const props = defineProps({
  42. placeholder: {
  43. type: String,
  44. default: "this is placeholder",
  45. },
  46. replayOrReferenceMessage: {
  47. type: Object,
  48. default: () => ({}),
  49. required: false,
  50. },
  51. isMuted: {
  52. type: Boolean,
  53. default: true,
  54. },
  55. muteText: {
  56. type: String,
  57. default: "",
  58. },
  59. enableInput: {
  60. type: Boolean,
  61. default: true,
  62. },
  63. enableAt: {
  64. type: Boolean,
  65. default: true,
  66. },
  67. enableTyping: {
  68. type: Boolean,
  69. default: true,
  70. },
  71. isGroup: {
  72. type: Boolean,
  73. default: false,
  74. },
  75. jobInfoData: {
  76. type: Object,
  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. let url = config.baseUrl + "/recruit/recruitCommunicate";
  111. const header = {
  112. "Content-Type": "application/json",
  113. Authorization: uni.getStorageSync("token")
  114. ? uni.getStorageSync("token")
  115. : uni.getStorageSync("unitoken"),
  116. };
  117. let data = {
  118. companyUserId: props.jobInfoData.companyUserId,
  119. msgContent: {
  120. text: {
  121. text: messageList[0].payload.text,
  122. },
  123. },
  124. msgType: "TIMTextElem",
  125. postId: props.jobInfoData.postId,
  126. recruitUserId: props.jobInfoData.recruitUserId,
  127. sendType: 0,
  128. status: props.jobInfoData.status,
  129. };
  130. let loginType = uni.getStorageSync("loginType");
  131. if (loginType == 1) {
  132. data.sendType = 1;
  133. }
  134. uni.request({
  135. url: url,
  136. method: "POST",
  137. header: header,
  138. data: data,
  139. success: function (res) {
  140. console.log(res.data);
  141. },
  142. });
  143. };
  144. const insertAt = (atInfo: any) => {
  145. if (!allInsertedAtInfo?.has(atInfo?.id)) {
  146. allInsertedAtInfo?.set(atInfo?.id, atInfo?.label);
  147. }
  148. inputText.value += atInfo?.label;
  149. };
  150. const getEditorContent = () => {
  151. let text = inputText.value;
  152. text = transformEmojiValueToKey(text);
  153. const atUserList: string[] = [];
  154. allInsertedAtInfo?.forEach((value: string, key: string) => {
  155. if (text?.includes("@" + value)) {
  156. atUserList.push(key);
  157. }
  158. });
  159. const payload: ISendMessagePayload = {
  160. text,
  161. };
  162. if (atUserList?.length) {
  163. payload.atUserList = atUserList;
  164. }
  165. return [
  166. {
  167. type: "text",
  168. payload,
  169. },
  170. ];
  171. };
  172. const resetEditor = () => {
  173. inputText.value = "";
  174. inputContentEmpty.value = true;
  175. allInsertedAtInfo?.clear();
  176. };
  177. const setEditorContent = (content: any) => {
  178. inputText.value = content;
  179. };
  180. const onBlur = () => {
  181. inputBlur.value = true;
  182. };
  183. const onFocus = (e: any) => {
  184. inputBlur.value = false;
  185. emits("onFocus", e?.detail?.height);
  186. };
  187. const isEditorContentEmpty = () => {
  188. inputContentEmpty.value = inputText?.value?.length ? false : true;
  189. };
  190. const onInput = (e: any) => {
  191. // uniapp 识别 @ 消息
  192. const text = e?.detail?.value;
  193. isEditorContentEmpty();
  194. if (props.isGroup && (text.endsWith("@") || text.endsWith("@\n"))) {
  195. TUIGlobal?.hideKeyboard();
  196. emits("onAt", true);
  197. }
  198. };
  199. watch(
  200. () => [inputContentEmpty.value, inputBlur.value],
  201. (newVal: any, oldVal: any) => {
  202. if (newVal !== oldVal) {
  203. emits("onTyping", inputContentEmpty.value, inputBlur.value);
  204. }
  205. },
  206. {
  207. immediate: true,
  208. deep: true,
  209. }
  210. );
  211. function onCurrentConversationUpdated(conversation: IConversationModel) {
  212. currentConversation.value = conversation;
  213. }
  214. defineExpose({
  215. insertAt,
  216. resetEditor,
  217. setEditorContent,
  218. getEditorContent,
  219. });
  220. </script>
  221. <style lang="scss" scoped>
  222. @import "../../../assets/styles/common";
  223. .message-input-container {
  224. display: flex;
  225. flex-direction: column;
  226. flex: 1;
  227. height: calc(100% - 13px);
  228. width: calc(100% - 20px);
  229. padding: 3px 10px 10px;
  230. overflow: hidden;
  231. &-h5 {
  232. flex: 1;
  233. height: auto;
  234. background: #fff;
  235. border-radius: 9.4px;
  236. padding: 7px 0 7px 10px;
  237. font-size: 16px !important;
  238. max-height: 86px;
  239. }
  240. .message-input-mute {
  241. flex: 1;
  242. display: flex;
  243. color: #999;
  244. font-size: 14px;
  245. justify-content: center;
  246. align-items: center;
  247. }
  248. .message-input-area {
  249. flex: 1;
  250. display: flex;
  251. overflow-y: scroll;
  252. min-height: 20px;
  253. }
  254. }
  255. </style>