index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <template>
  2. <view class="chat-input-bar-container">
  3. <reply-message-container
  4. ref="replyMessageContainerComps"
  5. v-show="isShowReplyMessageContainer"
  6. @onShowReplyMessageContainer="onShowReplyMessageContainer"
  7. :checkedPopupMsgBody="checkedPopupMsgBody"
  8. />
  9. <edit-message-container
  10. v-show="isShowEditMessageContainer"
  11. @onShowEditMessageContainer="onShowEditMessageContainer"
  12. />
  13. <view class="chat-input-bar">
  14. <!-- <template v-if="!isShowEditMessageContainer">
  15. <image
  16. class="chat-audio-icon-container"
  17. @click="onShowAudioMessageContainer"
  18. src="/static/images/new_ui/inputbar/audio_click_icon.png"
  19. ></image>
  20. </template> -->
  21. <!-- 文本输入input -->
  22. <view class="chat-input-container">
  23. <input
  24. class="chat-input"
  25. v-model="msgContent"
  26. :cursor-spacing="cursorSpacing"
  27. confirm-type="send"
  28. type="text"
  29. :always-embed="true"
  30. :adjust-position="false"
  31. @focus="onInputFocus"
  32. @blur="onInputBlur"
  33. @confirm="sendTextMessage"
  34. @input="onInput"
  35. />
  36. </view>
  37. <template v-if="isShowEditMessageContainer">
  38. <image
  39. class="chat-emoji-icon-container"
  40. @click="actionModifyMessage"
  41. src="/static/images/new_ui/message/send_edit_msg_icon.png"
  42. ></image>
  43. </template>
  44. </view>
  45. <!-- 输入框底部功能栏 -->
  46. <view class="chat-input-bottom-bar">
  47. <!-- 语音 -->
  48. <!-- <view class="input-bottom-bar-icon" @click="onShowAudioMessageContainer">
  49. <image
  50. src="/static/images/new_ui/inputbar/tofeipeng/icons/mic_stroke@2x.png"
  51. ></image>
  52. </view> -->
  53. <!-- 图库 -->
  54. <view class="input-bottom-bar-icon" @click="openPhotoAlbum('album')">
  55. <image
  56. src="/static/images/new_ui/inputbar/tofeipeng/icons/img@2x.png"
  57. ></image>
  58. </view>
  59. <!-- 拍照 -->
  60. <view class="input-bottom-bar-icon" @click="openPhotoAlbum('camera')">
  61. <image
  62. src="/static/images/new_ui/inputbar/tofeipeng/icons/camera@2x.png"
  63. ></image>
  64. </view>
  65. <!-- 文件 -->
  66. <!-- <view class="input-bottom-bar-icon">
  67. <image
  68. src="/static/images/new_ui/inputbar/tofeipeng/icons/folder@2x.png"
  69. ></image>
  70. </view> -->
  71. <!-- <SendFileMessage
  72. ref="fileComps"
  73. @onCloseAllShowContainer="onCloseAllShowContainer"
  74. >
  75. <view class="input-bottom-bar-icon">
  76. <image
  77. src="/static/images/new_ui/inputbar/tofeipeng/icons/folder@2x.png"
  78. ></image>
  79. </view>
  80. </SendFileMessage> -->
  81. <!-- emoji -->
  82. <view class="input-bottom-bar-icon" @click="onShowEmojiIconContainer">
  83. <image
  84. src="/static/images/new_ui/inputbar/tofeipeng/icons/face@2x.png"
  85. ></image>
  86. </view>
  87. <!-- 更多 -->
  88. <!-- <view class="input-bottom-bar-icon" @click="onShowMoreFuncContainer">
  89. <image
  90. src="/static/images/new_ui/inputbar/tofeipeng/icons/plus_in_circle@2x.png"
  91. ></image>
  92. </view> -->
  93. </view>
  94. <!-- 音频发送板块 -->
  95. <view v-if="isShowAudioMessageContainer" class="chat-audio-container">
  96. <RecordAudioContainer
  97. @changeRecordAudioContainer="
  98. () => (isShowAudioMessageContainer = false)
  99. "
  100. />
  101. </view>
  102. <!-- Emoji Icon选择板块 -->
  103. <view v-if="isShowEmojiIconContainer" class="chat-emoji-picker-container">
  104. <EmojiPickerContainer @appendEmojiIcon="appendEmojiIcon" />
  105. </view>
  106. <!-- 更多功能选择板块 -->
  107. <view v-if="isShowMoreFuncContainer" class="chat-more-icon-container">
  108. <MoreFuncContainer @onCloseAllShowContainer="onCloseAllShowContainer" />
  109. </view>
  110. <!-- 发送图片 -->
  111. <SendImageMessage
  112. ref="imageComps"
  113. @onCloseAllShowContainer="onCloseAllShowContainer"
  114. />
  115. <!-- 软键盘弹起占位容器 -->
  116. <view
  117. class="softkeyword-placeholder"
  118. :style="{ height: softkeyboardHeight + 'px' }"
  119. ></view>
  120. <!-- #ifdef H5 || APP-PLUS -->
  121. <!-- 小程序的宿主一般自带底部安全区 -->
  122. <view class="chat-input-bar-bottom-placeholder"></view>
  123. <!-- #endif -->
  124. </view>
  125. </template>
  126. <script>
  127. import { EMClient } from '@/EaseIM';
  128. import { CHAT_TYPE } from '@/EaseIM/constant';
  129. import { emMessages } from '@/EaseIM/emApis';
  130. import RecordAudioContainer from './recordAudioContainer';
  131. import EmojiPickerContainer from './emojiPickerContainer';
  132. import MoreFuncContainer from './moreFuncContainer';
  133. import ReplyMessageContainer from './replyMessageContainer';
  134. import EditMessageContainer from './editMessageContainer';
  135. import SendImageMessage from './inputMessages/sendImageMessage';
  136. import SendFileMessage from './inputMessages/sendFileMessage';
  137. import { MESSAGE_TYPE } from '../../../EaseIM/constant';
  138. const { sendDisplayMessages, sendCommandMessages, modifyDisplayMessages } =
  139. emMessages();
  140. export default {
  141. name: 'chat-input-bar',
  142. components: {
  143. RecordAudioContainer,
  144. EmojiPickerContainer,
  145. MoreFuncContainer,
  146. ReplyMessageContainer,
  147. EditMessageContainer,
  148. SendImageMessage,
  149. SendFileMessage,
  150. },
  151. props: {
  152. checkedPopupMsgBody: {
  153. type: Object,
  154. default: () => ({}),
  155. },
  156. },
  157. data() {
  158. return {
  159. msgContent: '',
  160. cursorSpacing: 20,
  161. softkeyboardHeight: 0,
  162. isShowAudioMessageContainer: false,
  163. isShowEmojiIconContainer: false,
  164. isShowMoreFuncContainer: false,
  165. isShowReplyMessageContainer: false,
  166. isShowEditMessageContainer: false,
  167. isOpenedTypingBeginStatus: false,
  168. };
  169. },
  170. mounted() {
  171. // #ifndef APP-NVUE
  172. const query = uni.createSelectorQuery().in(this);
  173. query
  174. .select('.chat-input-bar-bottom-placeholder')
  175. .boundingClientRect((data) => {
  176. if (data && data.height) {
  177. this.cursorSpacing = data.height + 20;
  178. }
  179. })
  180. .exec();
  181. // #endif
  182. this.getLocalInputingStatus();
  183. //监听键盘高度变化
  184. //监听键盘抬起事件
  185. uni.onKeyboardHeightChange &&
  186. uni.onKeyboardHeightChange(this.listenerKeyboardHeight);
  187. },
  188. computed: {
  189. chattingId() {
  190. return this.$store.getters.chattingId;
  191. },
  192. chattingChatType() {
  193. return this.$store.getters.chattingChatType;
  194. },
  195. },
  196. methods: {
  197. onInputFocus() {
  198. // input focus的时候重新设置一下input内容以修复在微信小程序&QQ小程序中input focus后位置偏移的bug
  199. // #ifdef MP-WEIXIN || MP-QQ
  200. // this.msgContent += ' ';
  201. // this.$nextTick(() => {
  202. // this.msgContent = this.msgContent.slice(0, -1);
  203. // });
  204. // #endif
  205. this.onCloseAllShowContainer();
  206. },
  207. listenerKeyboardHeight(e) {
  208. if (e.height > 0) {
  209. this.softkeyboardHeight = e.height;
  210. } else {
  211. this.softkeyboardHeight = 0;
  212. }
  213. },
  214. onInputBlur() {
  215. this.onCloseAllShowContainer();
  216. },
  217. onInput() {
  218. if (
  219. this.isShowEditMessageContainer === false &&
  220. this.isOpenedTypingBeginStatus
  221. ) {
  222. //节流正在输入中信令发送调用 10s发送一次。
  223. uni.$u.throttle(this.sendTypingBeginMessage, 10000, true);
  224. }
  225. },
  226. //发送文本消息
  227. async sendTextMessage() {
  228. if (this.msgContent.match(/^\s*$/)) {
  229. uni.showToast({
  230. title: '请输入内容',
  231. icon: 'none',
  232. });
  233. return;
  234. }
  235. let params = {
  236. // 消息类型。
  237. type: 'txt',
  238. // 消息内容。
  239. msg: this.msgContent,
  240. // 消息接收方:单聊为对方用户 ID,群聊和聊天室分别为群组 ID 和聊天室 ID。
  241. to: this.chattingId,
  242. // 会话类型:单聊、群聊和聊天室分别为 `singleChat`、`groupChat` 和 `chatRoom`。
  243. chatType: this.chattingChatType,
  244. ext: {},
  245. };
  246. //如果此时为编辑模式则直接return 并调用编辑消息
  247. if (this.isShowEditMessageContainer) {
  248. this.actionModifyMessage();
  249. return;
  250. }
  251. //检测是否存在引用消息
  252. if (this.isShowReplyMessageContainer) {
  253. params.ext.msgQuote = {};
  254. params.ext.msgQuote =
  255. this.$refs.replyMessageContainerComps.extractMessageBodyValue();
  256. console.log('>>>存在引用', params);
  257. }
  258. try {
  259. const res = await sendDisplayMessages({ ...params });
  260. console.log('>>>>>文本消息发送成功', res);
  261. } catch (error) {
  262. console.log('>>>>>文本消息发送失败', error);
  263. if (error.type === 508) {
  264. uni.showToast({
  265. title: '发送内容不合规',
  266. icon: 'none',
  267. });
  268. } else {
  269. uni.showToast({
  270. title: '消息发送失败',
  271. icon: 'none',
  272. });
  273. }
  274. } finally {
  275. this.msgContent = '';
  276. this.onCloseAllShowContainer();
  277. this.isShowReplyMessageContainer &&
  278. (this.isShowReplyMessageContainer = false);
  279. }
  280. },
  281. //编辑修改已发送文本消息
  282. async actionModifyMessage() {
  283. const editMessageContent = {
  284. msg: this.msgContent,
  285. to: this.checkedPopupMsgBody.to,
  286. id: this.checkedPopupMsgBody.id,
  287. chatType: CHAT_TYPE.SINGLE_CHAT,
  288. };
  289. try {
  290. const res = await modifyDisplayMessages(editMessageContent);
  291. console.log('>>>>>>消息修改成功', res);
  292. } catch (error) {
  293. console.log('>>>>>>修改消息失败', error);
  294. if (error?.type === 50) {
  295. uni.showToast({
  296. icon: 'none',
  297. title: '该消息可编辑次数已达上限',
  298. });
  299. } else {
  300. uni.showToast({
  301. icon: 'none',
  302. title: '消息编辑失败请稍后重试',
  303. });
  304. }
  305. } finally {
  306. this.isShowEditMessageContainer = false;
  307. this.msgContent = '';
  308. }
  309. },
  310. //获取当前键入中开启状态
  311. getLocalInputingStatus() {
  312. const res = uni.getStorageSync(`EM_${EMClient.user}_GENNERAL_CONFIG`);
  313. const { isShowInputingStatus } = res || {};
  314. this.isOpenedTypingBeginStatus = isShowInputingStatus;
  315. },
  316. //发送正在键入command
  317. async sendTypingBeginMessage() {
  318. const params = {
  319. type: MESSAGE_TYPE.CMD,
  320. // 消息类型。
  321. action: 'TypingBegin',
  322. // 消息接收方:单聊为对方用户 ID,群聊和聊天室分别为群组 ID 和聊天室 ID。
  323. to: this.chattingId,
  324. // 会话类型:单聊、群聊和聊天室分别为 `singleChat`、`groupChat` 和 `chatRoom`。
  325. chatType: this.chattingChatType,
  326. };
  327. try {
  328. await sendCommandMessages(params);
  329. } catch (error) {
  330. console.log('>>>>>发送失败', error);
  331. }
  332. },
  333. onShowAudioMessageContainer() {
  334. this.isShowAudioMessageContainer = !this.isShowAudioMessageContainer;
  335. this.isShowEmojiIconContainer && (this.isShowEmojiIconContainer = false);
  336. this.isShowMoreFuncContainer && (this.isShowMoreFuncContainer = false);
  337. this.$emit('resetScrollHeight', 200);
  338. },
  339. onShowEmojiIconContainer() {
  340. this.isShowEmojiIconContainer = !this.isShowEmojiIconContainer;
  341. this.isShowAudioMessageContainer &&
  342. (this.isShowAudioMessageContainer = false);
  343. this.isShowMoreFuncContainer && (this.isShowMoreFuncContainer = false);
  344. },
  345. onShowMoreFuncContainer() {
  346. this.isShowMoreFuncContainer = !this.isShowMoreFuncContainer;
  347. this.isShowAudioMessageContainer &&
  348. (this.isShowAudioMessageContainer = false);
  349. this.isShowEmojiIconContainer && (this.isShowEmojiIconContainer = false);
  350. },
  351. onShowReplyMessageContainer() {
  352. this.isShowReplyMessageContainer = !this.isShowReplyMessageContainer;
  353. this.isShowEditMessageContainer &&
  354. (this.isShowEditMessageContainer = false);
  355. this.onCloseAllShowContainer();
  356. },
  357. onShowEditMessageContainer() {
  358. if (this.isShowEditMessageContainer === false) {
  359. this.msgContent = this.checkedPopupMsgBody.msg;
  360. }
  361. this.isShowEditMessageContainer = !this.isShowEditMessageContainer;
  362. this.isShowReplyMessageContainer &&
  363. (this.isShowReplyMessageContainer = false);
  364. this.onCloseAllShowContainer();
  365. },
  366. onCloseAllShowContainer() {
  367. this.isShowAudioMessageContainer = false;
  368. this.isShowEmojiIconContainer = false;
  369. this.isShowMoreFuncContainer = false;
  370. },
  371. appendEmojiIcon(emoji) {
  372. this.msgContent += emoji;
  373. },
  374. appendReEditTextMessage(content) {
  375. if (this.msgContent !== '') {
  376. this.msgContent += content;
  377. } else {
  378. this.msgContent = content;
  379. }
  380. },
  381. openPhotoAlbum(type) {
  382. if (type === 'camera') {
  383. this.$refs.imageComps.openCamera();
  384. } else if (type === 'album') {
  385. this.$refs.imageComps.openPhotoAlbum();
  386. }
  387. },
  388. },
  389. beforeDestroy() {
  390. uni.offKeyboardHeightChange &&
  391. uni.offKeyboardHeightChange(this.listenerKeyboardHeight);
  392. },
  393. };
  394. </script>
  395. <style scoped>
  396. .chat-input-bar {
  397. display: flex;
  398. flex-direction: row;
  399. align-items: center;
  400. border-top: solid 1px #f5f5f5;
  401. background-color: #f8f8f8;
  402. padding: 10rpx 15rpx;
  403. }
  404. .chat-input-bar-bottom-placeholder {
  405. background-color: #f8f8f8;
  406. /* #ifndef APP-PLUS */
  407. height: env(safe-area-inset-bottom);
  408. /* #endif */
  409. }
  410. .softkeyword-placeholder {
  411. width: 100%;
  412. background-color: white;
  413. }
  414. .chat-input-container {
  415. flex: 1;
  416. display: flex;
  417. /* #ifndef APP-NVUE */
  418. padding: 15rpx;
  419. /* #endif */
  420. /* #ifdef APP-NVUE */
  421. padding: 10rpx;
  422. /* #endif */
  423. background-color: white;
  424. border-radius: 10rpx;
  425. }
  426. .chat-input-bottom-bar {
  427. width: 100%;
  428. height: 100rpx;
  429. display: flex;
  430. flex-direction: row;
  431. justify-content: space-between;
  432. align-items: center;
  433. padding: 0 50rpx;
  434. box-sizing: border-box;
  435. background-color: white;
  436. }
  437. .input-bottom-bar-icon {
  438. width: 60rpx;
  439. height: 60rpx;
  440. }
  441. .input-bottom-bar-icon image {
  442. width: 100%;
  443. height: 100%;
  444. }
  445. .chat-input {
  446. flex: 1;
  447. font-size: 28rpx;
  448. }
  449. .chat-input-send {
  450. background-color: #007aff;
  451. margin: 10rpx 10rpx 10rpx 20rpx;
  452. border-radius: 10rpx;
  453. padding: 10rpx 30rpx;
  454. }
  455. .chat-input-send-text {
  456. color: white;
  457. font-size: 26rpx;
  458. }
  459. .chat-audio-icon-container,
  460. .chat-emoji-icon-container,
  461. .chat-cancel-icon-container {
  462. width: 30px;
  463. height: 30px;
  464. }
  465. .chat-audio-container {
  466. width: 100%;
  467. height: 200px;
  468. display: flex;
  469. flex-direction: row;
  470. align-items: center;
  471. justify-content: space-around;
  472. background: #fff;
  473. }
  474. .chat-emoji-picker-container {
  475. width: 100%;
  476. /* height: 300px; */
  477. background: #fff;
  478. }
  479. </style>