index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. <template>
  2. <view class="chating_footer" :style="{ paddingBottom: keyboardHeight + 'px' }">
  3. <view class="forbidden_footer" v-if="getPlaceholder.length > 0">
  4. <image style="margin-right: 8rpx;width:50rpx" src="/static/images/forbidden_footer.png" mode="aspectFit" />
  5. <text>{{ getPlaceholder }}</text>
  6. </view>
  7. <view v-if="getPlaceholder.length <= 0" :style="{ 'pointer-events': getPlaceholder ? 'none' : 'auto' }">
  8. <view class="chat_footer">
  9. <image v-if="showRecord"
  10. @click="updateRecordBtn"
  11. class="footer_icon_left"
  12. src="/pages_im/static/images/chating_footer_audio_recording.png" />
  13. <image v-else
  14. @click="updateRecordBtn"
  15. class="footer_icon_left"
  16. src="/pages_im/static/images/chating_footer_audio.png" />
  17. <view class="input_content">
  18. <CustomEditor
  19. :placeholder="getPlaceholder"
  20. v-if="!showRecord"
  21. class="custom_editor"
  22. ref="customEditor"
  23. @ready="editorReady"
  24. @focus="editorFocus"
  25. @blur="editorBlur"
  26. @input="editorInput"
  27. @tryAt="showAtPanel"/>
  28. <view v-if="storeQuoteMessage" class="quote_message">
  29. <view class="content">
  30. <u-parse :content="getQuotedContent" />
  31. </view>
  32. <image @click="cancelQuote" style="width: 16px; height: 16px" src="/pages_im/static/images/chating_footer_quote_close.png" alt="" />
  33. </view>
  34. <view class="record_btn" ref="recordBtn" @touchstart="recordStartTouch" @longpress="longpressRecord" @touchmove="touchMoveSpeech" @touchend="endRecord" v-if="showRecord">
  35. <text class="record_text">{{ recording ? '松手发送' : '按住说话' }}</text>
  36. </view>
  37. </view>
  38. <view class="footer_action_area">
  39. <image @click.prevent="updateEmojiBar" class="footer_icon_right" src="/pages_im/static/images/chating_footer_emoji.png" alt="" />
  40. <image v-if="!hasContent" @click.prevent="updateActionBar" class="footer_icon_right" style="margin-right: 0;" src="/pages_im/static/images/chating_footer_add.png" alt="" />
  41. <view class="btnSend" v-if="hasContent" @touchend.prevent="sendTextMessage">
  42. <text class="btnSend_text">发送</text>
  43. </view>
  44. </view>
  45. </view>
  46. <view class="panel-container" :style="{ height: (actionBarVisible || emojiBarVisible) ? panelHeight+'rpx' : '0px' }">
  47. <chating-action-bar @sendMessage="sendMessage" @prepareMediaMessage="prepareMediaMessage" @enterSubPage="$emit('enterSubPage')" v-if="actionBarVisible" />
  48. <chating-emoji-bar @insertEmoji="emojiClick" @backspace="backspace" v-if="emojiBarVisible" />
  49. </view>
  50. <u-action-sheet
  51. :safeAreaInsetBottom="true"
  52. round="12"
  53. :actions="actionSheetMenu"
  54. @select="selectClick"
  55. :closeOnClickOverlay="true"
  56. :closeOnClickAction="true"
  57. :show="showActionSheet"
  58. @close="showActionSheet = false"></u-action-sheet>
  59. <record-overlay @recordFinish="recordFinish" ref="recordOverlayRef" :visible="recording" />
  60. </view>
  61. </view>
  62. </template>
  63. <script>
  64. import { mapGetters, mapActions } from 'vuex';
  65. // import { formatInputHtml, getPurePath, html2Text } from '../../../../../util/common';
  66. import { parseMessageByType, getIMSDK} from '../../../../../util/imCommon';
  67. import { ChatingFooterActionTypes, UpdateMessageTypes, GroupMemberListTypes, PageEvents } from '../../../../../constant';
  68. import IMSDK, { GroupMemberRole, GroupStatus, IMMethods, MessageStatus, MessageType, SessionType } from 'openim-uniapp-polyfill';
  69. import UParse from '../../../../../components/gaoyia-parse/parse.vue';
  70. import CustomEditor from './CustomEditor.vue';
  71. import ChatingActionBar from './ChatingActionBar.vue';
  72. import ChatingEmojiBar from './ChatingEmojiBar.vue';
  73. import RecordOverlay from './RecordOverlay.vue';
  74. import { gotoAppPermissionSetting, requestAndroidPermission, judgeIosPermission } from '../../../../../util/permission';
  75. const needClearTypes = [
  76. MessageType.TextMessage,
  77. MessageType.AtTextMessage,
  78. MessageType.QuoteMessage
  79. ];
  80. export default {
  81. components: {
  82. CustomEditor,
  83. ChatingActionBar,
  84. ChatingEmojiBar,
  85. UParse,
  86. RecordOverlay
  87. },
  88. props: {
  89. footerOutsideFlag: Number
  90. },
  91. data() {
  92. return {
  93. customEditorCtx: null,
  94. inputHtml: '',
  95. showRecord: false,
  96. actionBarVisible: false,
  97. emojiBarVisible: false,
  98. isInputFocus: false,
  99. actionSheetMenu: [],
  100. showActionSheet: false,
  101. recording: false,
  102. startPageY: 0,
  103. recordTop: 0,
  104. conversationID: null,
  105. isJoinGroup: true,
  106. isSending: false,
  107. keyboardHeight: 0,
  108. panelHeight:0,
  109. };
  110. },
  111. computed: {
  112. ...mapGetters(['storeQuoteMessage', 'storeCurrentConversation', 'storeCurrentGroup', 'storeCurrentMemberInGroup', 'storeBlackList', 'storeRevokeMap','timStore', 'storeCurrentUserID']),
  113. getQuotedContent() {
  114. if (!this.storeQuoteMessage) {
  115. return '';
  116. }
  117. return '';
  118. return `回复${parseMessageByType(this.storeQuoteMessage, this.storeCurrentUserID)}`;
  119. },
  120. hasContent() {
  121. return this.html2Text(this.inputHtml) !== '';
  122. },
  123. isNomal() {
  124. return this.storeCurrentMemberInGroup?.roleLevel === GroupMemberRole.Nomal;
  125. },
  126. getPlaceholder() {
  127. if (!this.storeCurrentConversation?.conversationID) return '';
  128. const isSingle = this.storeCurrentConversation.conversationType === SessionType.Single;
  129. if (!isSingle && this.storeCurrentGroup?.status === GroupStatus.Muted) {
  130. return !this.isNomal ? '' : '群主或管理员已开启全体禁言';
  131. }
  132. if (!isSingle && !this.isJoinGroup) {
  133. return '您已不在该群组';
  134. }
  135. if (!isSingle && this.storeCurrentMemberInGroup?.muteEndTime > Date.now()) {
  136. return `您已被管理员或群主禁言!`;
  137. }
  138. if (isSingle && this.storeBlackList?.find((black) => black.userID === this.storeCurrentConversation.userID)) {
  139. return '对方已被拉入黑名单';
  140. }
  141. return '';
  142. },
  143. imType() {
  144. return this.timStore?.imType ?? '' ;
  145. },
  146. orderId() {
  147. return this.timStore?.orderId ?? '' ;
  148. },
  149. orderType() {
  150. return this.timStore?.orderType ?? '' ;
  151. },
  152. },
  153. watch: {
  154. footerOutsideFlag(newVal) {
  155. this.onClickActionBarOutside();
  156. this.onClickEmojiBarOutside();
  157. }
  158. },
  159. mounted() {
  160. this.setIMListener();
  161. this.setKeyboardListener();
  162. if (this.storeCurrentConversation?.groupID) {
  163. IMSDK.asyncApi(IMSDK.IMMethods.IsJoinGroup, IMSDK.uuid(), this.storeCurrentConversation.groupID).then((res) => {
  164. this.isJoinGroup = res.data;
  165. });
  166. }
  167. this.conversationID = this.storeCurrentConversation?.conversationID;
  168. if (this.storeCurrentConversation?.draftText) {
  169. this.parseDraftText(this.storeCurrentConversation.draftText);
  170. }
  171. if (this.conversationID) {
  172. IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
  173. conversationID: this.conversationID,
  174. draftText: ''
  175. });
  176. }
  177. this.panelHeight=this.$companyUserIsLogin()?550:380;
  178. uni.$on(PageEvents.ReEditMessage, this.setReEditMessage);
  179. uni.$on("sendIM", this.sendPackageImMsg);
  180. uni.$on("sendIMCts", this.sendCtsImMsg);
  181. },
  182. beforeDestroy() {
  183. this.disposeIMListener();
  184. this.disposeKeyboardListener();
  185. if (this.inputHtml && this.inputHtml.trim()) {
  186. IMSDK.asyncApi(IMMethods.SetConversationDraft, IMSDK.uuid(), {
  187. conversationID: this.conversationID,
  188. draftText: this.html2Text(this.inputHtml)
  189. });
  190. }
  191. uni.$off("sendIM", this.sendPackageImMsg);
  192. uni.$off("sendIMCts", this.sendCtsImMsg);
  193. },
  194. methods: {
  195. ...mapActions('message', ['pushNewMessage', 'updateOneMessage', 'updateQuoteMessageRevoke']),
  196. setReEditMessage(id) {
  197. this.customEditorCtx.clear();
  198. setTimeout(() => {
  199. this.$refs.customEditor.insertEmoji(this.storeRevokeMap[id].text);
  200. this.$store.commit('message/SET_QUOTE_MESSAGE', this.storeRevokeMap[id].quoteMessage);
  201. }, 50);
  202. },
  203. parseDraftText(draftText) {
  204. if (!draftText) return;
  205. let currentText = '';
  206. for (let i = 0; i < draftText.length; i++) {
  207. const currentChar = draftText[i];
  208. if (currentChar === '<') {
  209. if (currentText) {
  210. const text = currentText;
  211. setTimeout(() => {
  212. this.$refs.customEditor.insertEmoji(text);
  213. }, 100);
  214. currentText = '';
  215. }
  216. const imgEndIndex = draftText.indexOf('>', i);
  217. if (imgEndIndex !== -1) {
  218. const imgTag = draftText.substring(i, imgEndIndex + 1);
  219. if (imgTag.includes('class="at_el"')) {
  220. const customDataReg = /data-custom=".+"/;
  221. const atInfoArr = imgTag.match(customDataReg)[0].slice(13, -1).split('&amp;');
  222. this.$refs.customEditor.createCanvasData(atInfoArr[0].slice(7), atInfoArr[1].slice(15));
  223. }
  224. i = imgEndIndex;
  225. }
  226. } else {
  227. currentText += currentChar;
  228. }
  229. }
  230. if (currentText) {
  231. setTimeout(() => {
  232. this.$refs.customEditor.insertEmoji(currentText);
  233. }, 700);
  234. }
  235. },
  236. recordStartTouch(e) {
  237. if (e.touches && e.touches.length > 0) {
  238. this.startPageY = e.touches[0].pageY;
  239. }
  240. },
  241. longpressRecord() {
  242. this.recording = true;
  243. this.recordTop = this.startPageY - 50;
  244. },
  245. touchMoveSpeech(e) {
  246. uni.$u.throttle(this.$refs.recordOverlayRef.touchMoveSpeech(e, this.recordTop), 250);
  247. },
  248. endRecord() {
  249. this.recording = false;
  250. this.$refs.recordOverlayRef.checkStopType();
  251. },
  252. async recordFinish(path, duration) {
  253. const message = await IMSDK.asyncApi(IMMethods.CreateSoundMessageFromFullPath, IMSDK.uuid(), {
  254. soundPath: this.getPurePath(path),
  255. duration
  256. });
  257. this.sendMessage(message);
  258. },
  259. async createTextMessage() {
  260. let message = '';
  261. const { text, atUsersInfo } = this.formatInputHtml(this.inputHtml);
  262. if (atUsersInfo.length === 0) {
  263. if (this.storeQuoteMessage) {
  264. message = await IMSDK.asyncApi(IMMethods.CreateQuoteMessage, IMSDK.uuid(), {
  265. text,
  266. message: this.storeQuoteMessage
  267. });
  268. } else {
  269. message = await IMSDK.asyncApi(IMMethods.CreateTextMessage, IMSDK.uuid(), text);
  270. }
  271. } else {
  272. const quoteMessage = this.storeQuoteMessage ? this.storeQuoteMessage : '';
  273. message = await IMSDK.asyncApi(IMMethods.CreateTextAtMessage, IMSDK.uuid(), {
  274. text,
  275. atUserIDList: atUsersInfo.map((item) => item.atUserID),
  276. atUsersInfo,
  277. message: quoteMessage
  278. });
  279. }
  280. return message;
  281. },
  282. async sendTextMessage() {
  283. if (!this.hasContent) return;
  284. IMSDK.asyncApi('changeInputStates', IMSDK.uuid(), {
  285. conversationID: this.storeCurrentConversation.conversationID,
  286. focus: false
  287. });
  288. try {
  289. this.isSending = true;
  290. const message = await this.createTextMessage();
  291. await this.sendMessage(message);
  292. } catch (error) {
  293. console.error('发送消息失败:', error);
  294. uni.showToast({
  295. title: '发送失败',
  296. icon: 'none'
  297. });
  298. } finally {
  299. this.isSending = false;
  300. }
  301. },
  302. async sendCtsPackageMessage(item){
  303. let payloadJson={
  304. payload:{
  305. data:"package",
  306. extension:{
  307. packageId:item.packageId,
  308. title:item.packageName,
  309. companyId:item.companyId,
  310. companyUserId:item.companyUserId,
  311. image:item.imgUrl
  312. }
  313. }
  314. };
  315. let message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
  316. data: JSON.stringify(payloadJson),
  317. extension: '',
  318. description: 'package'
  319. });
  320. this.sendMessage(message);
  321. },
  322. sendPackageImMsg(item){
  323. console.log("qxj package:"+item.packageId);
  324. this.sendCtsPackageMessage(item);
  325. },
  326. async sendCtsMessage(item){
  327. let payloadJson={};
  328. if(item.type=="couponPackage"){
  329. payloadJson={
  330. payload:{
  331. data:item.type,
  332. extension:{
  333. couponId:item.couponId,
  334. title:item.title,
  335. price:item.price,
  336. minPrice:item.minPrice,
  337. limitTime:item.limitTime,
  338. companyId:item.companyId,
  339. companyUserId:item.companyUserId,
  340. }
  341. }
  342. };
  343. }
  344. if(item.type=="inquirySelect"){
  345. payloadJson={
  346. payload:{
  347. data:item.type,
  348. extension:{
  349. packageId:item.packageId,
  350. title:item.lable,
  351. type:item.value,
  352. companyId:item.companyId,
  353. companyUserId:item.companyUserId,
  354. }
  355. }
  356. };
  357. }
  358. let message = await IMSDK.asyncApi(IMMethods.CreateCustomMessage, IMSDK.uuid(), {
  359. data: JSON.stringify(payloadJson),
  360. extension: '',
  361. description: item.type
  362. });
  363. this.sendMessage(message);
  364. },
  365. sendCtsImMsg(item){
  366. this.sendCtsMessage(item);
  367. },
  368. sendMessage(message) {
  369. var customerData={
  370. type:this.timStore?.type,
  371. imType:this.timStore?.imType,
  372. orderId:this.timStore?.orderId,
  373. followId:this.timStore?.followId,
  374. orderType:this.timStore?.orderType
  375. }
  376. let userId=this.storeCurrentConversation.userID;
  377. if(userId!=undefined && (userId!="" || userId.length>0)){
  378. if(userId.indexOf('D')!==-1){
  379. message.ex=JSON.stringify(customerData);
  380. }
  381. }
  382. this.pushNewMessage(message);
  383. if (needClearTypes.includes(message.contentType)) {
  384. this.customEditorCtx.clear();
  385. }
  386. let offlinePushInfo={
  387. title:message.senderNickname,
  388. desc:"",
  389. desc: parseMessageByType(message, this.storeCurrentUserID),
  390. ex: "",
  391. iOSPushSound: "",
  392. iOSBadgeCount: true
  393. };
  394. this.$emit('scrollToBottom');
  395. IMSDK.asyncApi(IMMethods.SendMessage, IMSDK.uuid(), {
  396. recvID: this.storeCurrentConversation.userID,
  397. groupID: this.storeCurrentConversation.groupID,
  398. message,
  399. offlinePushInfo
  400. }).then(({ data }) => {
  401. this.updateOneMessage({
  402. message: data,
  403. isSuccess: true
  404. });
  405. this.$nextTick(() => {
  406. this.$emit('scrollToBottom');
  407. });
  408. }).catch(({ data, errCode, errMsg }) => {
  409. this.updateOneMessage({
  410. message: data,
  411. type: UpdateMessageTypes.KeyWords,
  412. keyWords: [
  413. {
  414. key: 'status',
  415. value: MessageStatus.Failed
  416. },
  417. {
  418. key: 'errCode',
  419. value: errCode
  420. }
  421. ]
  422. });
  423. });
  424. if (this.storeQuoteMessage) {
  425. this.$store.commit('message/SET_QUOTE_MESSAGE', undefined);
  426. }
  427. },
  428. cancelQuote() {
  429. this.$store.commit('message/SET_QUOTE_MESSAGE', undefined);
  430. },
  431. onClickActionBarOutside() {
  432. if (this.actionBarVisible) {
  433. this.actionBarVisible = false;
  434. }
  435. },
  436. onClickEmojiBarOutside() {
  437. if (this.emojiBarVisible) {
  438. this.emojiBarVisible = false;
  439. }
  440. },
  441. updateActionBar() {
  442. if (this.recording) {
  443. return;
  444. }
  445. if (this.showRecord) {
  446. this.showRecord = false;
  447. }
  448. // Toggle logic: if already visible, close it. If not, open it and close others.
  449. if (this.actionBarVisible) {
  450. this.actionBarVisible = false;
  451. } else {
  452. this.emojiBarVisible = false;
  453. this.actionBarVisible = true;
  454. if (this.keyboardHeight > 0) {
  455. uni.hideKeyboard();
  456. }
  457. }
  458. },
  459. updateEmojiBar() {
  460. if (this.recording) {
  461. return;
  462. }
  463. if (this.showRecord) {
  464. this.showRecord = false;
  465. }
  466. // Toggle logic
  467. if (this.emojiBarVisible) {
  468. this.emojiBarVisible = false;
  469. } else {
  470. this.actionBarVisible = false;
  471. this.emojiBarVisible = true;
  472. if (this.keyboardHeight > 0) {
  473. uni.hideKeyboard();
  474. }
  475. }
  476. },
  477. editorReady(e) {
  478. console.log("qxj editorReady");
  479. this.customEditorCtx = e.context;
  480. this.customEditorCtx.clear();
  481. },
  482. editorFocus() {
  483. //console.log("qxj editorFocus");
  484. this.isInputFocus = true;
  485. this.$emit('scrollToBottom');
  486. this.actionBarVisible = false;
  487. this.emojiBarVisible = false;
  488. },
  489. editorBlur() {
  490. //console.log("qxj editorBlur");
  491. this.isInputFocus = false;
  492. },
  493. editorInput(e) {
  494. //console.log("qxj editorInput");
  495. this.inputHtml = e.detail.html;
  496. uni.$u.throttle(this.updateTyping, 2000);
  497. },
  498. showAtPanel() {
  499. if (!this.$store.getters.storeCurrentConversation.groupID) return;
  500. uni.$u.route('/pages_im/pages/conversation/groupMemberList/index', {
  501. type: GroupMemberListTypes.ChooseAt,
  502. groupID: this.$store.getters.storeCurrentConversation.groupID
  503. });
  504. },
  505. backspace() {
  506. this.customEditorCtx.undo();
  507. },
  508. updateRecordBtn() {
  509. if (this.recording) {
  510. return;
  511. }
  512. if (!this.showRecord) {
  513. this.checkRecordPermission();
  514. }
  515. // Close panels if open
  516. this.emojiBarVisible = false;
  517. this.actionBarVisible = false;
  518. this.showRecord = !this.showRecord;
  519. if (this.showRecord) {
  520. if (this.keyboardHeight > 0) {
  521. uni.hideKeyboard();
  522. }
  523. } else {
  524. // Focus editor when switching back to text
  525. // this.$refs.editor?.focus();
  526. // nvue editor focus is tricky, usually requires user tap or specific method
  527. }
  528. },
  529. async checkRecordPermission() {
  530. if (uni.$u.os() === 'ios') {
  531. const iosFlag = judgeIosPermission('record');
  532. if (iosFlag === 0) {
  533. uni.$u.toast('您已禁止访问麦克风,请前往开启');
  534. setTimeout(() => gotoAppPermissionSetting(), 250);
  535. }
  536. if (iosFlag === -1) {
  537. let recorderManager = uni.getRecorderManager();
  538. recorderManager.onStop(() => (recorderManager = null));
  539. recorderManager.start();
  540. setTimeout(() => recorderManager.stop());
  541. }
  542. } else {
  543. const androidFlag = await requestAndroidPermission('android.permission.RECORD_AUDIO');
  544. if (androidFlag === -1) {
  545. uni.$u.toast('您已禁止访问麦克风,请前往开启');
  546. setTimeout(() => gotoAppPermissionSetting(), 250);
  547. }
  548. }
  549. },
  550. prepareMediaMessage(data) {
  551. this.selectClick(data);
  552. },
  553. updateTyping() {
  554. if (this.storeCurrentConversation?.conversationType === SessionType.Single) {
  555. IMSDK.asyncApi('changeInputStates', IMSDK.uuid(), {
  556. conversationID: this.storeCurrentConversation.conversationID,
  557. focus: true
  558. });
  559. }
  560. },
  561. emojiClick(emoji) {
  562. this.$refs.customEditor.insertEmoji(emoji);
  563. },
  564. batchCreateImageMesage(paths) {
  565. paths.forEach(async (path) => {
  566. const message = await IMSDK.asyncApi(IMMethods.CreateImageMessageFromFullPath, IMSDK.uuid(), this.getPurePath(path));
  567. this.sendMessage(message);
  568. });
  569. },
  570. selectClick(data) {
  571. if (data.index === 0) {
  572. if (data.type === ChatingFooterActionTypes.Album) {
  573. this.chooseOrShotImage(['album']).then((paths) => this.batchCreateImageMesage(paths));
  574. } else {
  575. this.chooseOrShotImage(['camera']).then((paths) => this.batchCreateImageMesage(paths));
  576. }
  577. }
  578. else {
  579. const whenGetFile = (data) => {
  580. const purePath = this.getPurePath(data.path);
  581. IMSDK.getVideoCover(purePath).then(async ({ path }) => {
  582. // console.log(this.getPurePath(path));
  583. const message = await IMSDK.asyncApi(IMMethods.CreateVideoMessageFromFullPath, IMSDK.uuid(), {
  584. videoPath: purePath,
  585. videoType: data.videoType,
  586. duration: Number(data.duration.toFixed()),
  587. snapshotPath: this.getPurePath(path)
  588. });
  589. this.sendMessage(message);
  590. });
  591. };
  592. if (data.type === ChatingFooterActionTypes.Album) {
  593. this.chooseOrShotVideo(['album']).then(whenGetFile);
  594. } else {
  595. this.chooseOrShotVideo(['camera']).then(whenGetFile);
  596. }
  597. }
  598. },
  599. chooseOrShotImage(sourceType) {
  600. return new Promise((resolve, reject) => {
  601. uni.chooseImage({
  602. count: 9,
  603. sizeType: ['compressed'],
  604. sourceType,
  605. success: function ({ tempFilePaths }) {
  606. resolve(tempFilePaths);
  607. },
  608. fail: function (err) {
  609. console.log(err);
  610. reject(err);
  611. }
  612. });
  613. });
  614. },
  615. chooseOrShotVideo(sourceType) {
  616. return new Promise((resolve, reject) => {
  617. uni.chooseVideo({
  618. compressed: true,
  619. sourceType,
  620. extension: ['mp4'],
  621. success: function ({ tempFilePath, duration }) {
  622. const idx = tempFilePath.lastIndexOf('.');
  623. const videoType = tempFilePath.slice(idx + 1);
  624. if (tempFilePath.includes('_doc/')) {
  625. tempFilePath = `file://${plus.io.convertLocalFileSystemURL(tempFilePath)}`;
  626. }
  627. // console.log(tempFilePath);
  628. resolve({
  629. path: tempFilePath,
  630. videoType,
  631. duration
  632. });
  633. },
  634. fail: function (err) {
  635. console.log(err);
  636. reject(err);
  637. }
  638. });
  639. });
  640. },
  641. insertAt(userID, nickname) {
  642. const { atUsersInfo } = this.formatInputHtml(this.inputHtml);
  643. if (atUsersInfo.find((item) => item.atUserID === userID)) {
  644. return;
  645. }
  646. this.$refs.customEditor.createCanvasData(userID, nickname);
  647. },
  648. sendProgressHandler({ data: { progress, message } }) {
  649. this.updateOneMessage({
  650. message,
  651. type: UpdateMessageTypes.KeyWords,
  652. keyWords: {
  653. key: 'uploadProgress',
  654. value: progress
  655. }
  656. });
  657. },
  658. joinedGroupChangeHandler({ data }) {
  659. if (data.groupID === this.storeCurrentConversation.groupID) {
  660. IMSDK.asyncApi(IMMethods.IsJoinGroup, IMSDK.uuid(), data.groupID).then((res) => {
  661. this.isJoinGroup = res.data;
  662. });
  663. }
  664. },
  665. setIMListener() {
  666. IMSDK.subscribe("SendMessageProgress", this.sendProgressHandler);
  667. uni.$on(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
  668. uni.$on(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
  669. },
  670. disposeIMListener() {
  671. IMSDK.unsubscribe("SendMessageProgress", this.sendProgressHandler);
  672. uni.$off(IMSDK.IMEvents.OnJoinedGroupDeleted, this.joinedGroupChangeHandler);
  673. uni.$off(IMSDK.IMEvents.OnJoinedGroupAdded, this.joinedGroupChangeHandler);
  674. },
  675. keyboardChangeHander({ height }) {
  676. this.keyboardHeight = height;
  677. if (height > 0) {
  678. if (this.emojiBarVisible) {
  679. this.emojiBarVisible = false;
  680. }
  681. if (this.actionBarVisible) {
  682. this.actionBarVisible = false;
  683. }
  684. this.$emit('scrollToBottom');
  685. }
  686. },
  687. setKeyboardListener() {
  688. // #ifdef APP-NVUE
  689. uni.onKeyboardHeightChange(this.keyboardChangeHander);
  690. // #endif
  691. },
  692. disposeKeyboardListener() {
  693. // #ifdef APP-NVUE
  694. uni.offKeyboardHeightChange(this.keyboardChangeHander);
  695. // #endif
  696. },
  697. html2Text(html){
  698. if (!html) {
  699. return "";
  700. }
  701. return html
  702. .replace(/<\/p><p>/g, "\n")
  703. .replace(/\&nbsp;/g, " ")
  704. .replace(/<p>/g, "")
  705. .replace(/<\/p>/g, "")
  706. .replace(/<br>/g, "")
  707. .trim();
  708. },
  709. getPurePath(path){
  710. const prefix = "file://";
  711. const relativeRrefix = "_doc/";
  712. if (path.includes(prefix)) {
  713. path = path.replace(prefix, "");
  714. }
  715. if (path.includes(relativeRrefix)) {
  716. path = plus.io.convertLocalFileSystemURL(path);
  717. }
  718. return path;
  719. },
  720. formatInputHtml(html){
  721. let atUsersInfo = [];
  722. let text = this.html2Text(html);
  723. const imgReg = new RegExp("(i?)(<img)([^>]+>)", "gmi");
  724. const customDataReg = /data-custom=".+"/;
  725. text = text.replace(imgReg, (img) => {
  726. if (img.includes('class="at_el"')) {
  727. const atInfoArr = img
  728. .match(customDataReg)[0]
  729. .slice(13, -1)
  730. .split("&amp;");
  731. atUsersInfo.push({
  732. atUserID: atInfoArr[0].slice(7),
  733. groupNickname: atInfoArr[1].slice(15),
  734. });
  735. return `@${atInfoArr[0].slice(7)} `;
  736. }
  737. if (img.includes('class="emoji_el"')) {
  738. return img.match(customDataReg)[0].slice(23, -1);
  739. }
  740. return "";
  741. });
  742. return {
  743. text,
  744. atUsersInfo,
  745. };
  746. }
  747. }
  748. };
  749. </script>
  750. <style lang="scss" scoped>
  751. .chating_footer {
  752. background-color: #f0f2f6;
  753. }
  754. .custom_editor {
  755. flex: 1;
  756. }
  757. .forbidden_footer {
  758. width: 750rpx;
  759. height: 112rpx;
  760. flex-direction: row;
  761. justify-content: center;
  762. align-items: center;
  763. background-color: #f0f2f6;
  764. }
  765. .chat_footer {
  766. flex-direction: row;
  767. align-items: flex-end;
  768. background-color: #f0f2f6;
  769. padding: 16rpx 20rpx;
  770. }
  771. .input_content {
  772. flex: 1;
  773. margin-left: 20rpx;
  774. margin-right: 20rpx;
  775. background-color: #ffffff;
  776. border-radius: 10rpx;
  777. min-height: 72rpx;
  778. /* padding: 10rpx; 移除了 padding,让 editor 占满点击区域 */
  779. }
  780. .record_btn {
  781. background-color: #ffffff;
  782. height: 72rpx;
  783. justify-content: center;
  784. align-items: center;
  785. border-radius: 10rpx;
  786. }
  787. .record_text {
  788. font-size: 30rpx;
  789. color: #333333;
  790. font-weight: bold;
  791. }
  792. .quote_message {
  793. flex-direction: row;
  794. align-items: center;
  795. justify-content: space-between;
  796. margin-top: 12rpx;
  797. padding: 8rpx;
  798. background-color: #f5f5f5;
  799. border-radius: 6rpx;
  800. }
  801. .quote_content {
  802. flex: 1;
  803. lines: 2;
  804. text-overflow: ellipsis;
  805. font-size: 24rpx;
  806. color: #666666;
  807. }
  808. .footer_action_area {
  809. flex-direction: row;
  810. align-items: center;
  811. height: 72rpx;
  812. }
  813. .footer_icon_left {
  814. width: 56rpx;
  815. height: 56rpx;
  816. margin-bottom: 8rpx;
  817. }
  818. .footer_icon_right {
  819. width: 56rpx;
  820. height: 56rpx;
  821. margin-right: 20rpx;
  822. }
  823. .btnSend {
  824. width: 110rpx;
  825. height: 64rpx;
  826. background-color: #1ab84d;
  827. border-radius: 8rpx;
  828. justify-content: center;
  829. align-items: center;
  830. }
  831. .btnSend_text {
  832. color: #fff;
  833. font-size: 28rpx;
  834. }
  835. .panel-container {
  836. width: 750rpx;
  837. background-color: #f0f2f6;
  838. overflow: hidden;
  839. }
  840. </style>