privateChat.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814
  1. <template>
  2. <view class="chatInterface" @contextmenu.prevent="">
  3. <view class="scroll-view">
  4. <image v-if="history.loading" class="history-loaded" src="/static/images/loading.svg"/>
  5. <view v-else :class="history.allLoaded ? 'history-loaded':'load'" @click="loadHistoryMessage(false)">
  6. <view>{{ history.allLoaded ? '已经没有更多的历史消息' : '点击获取历史消息' }}</view>
  7. </view>
  8. <checkbox-group @change="selectMessages">
  9. <!--消息记录-->
  10. <view v-for="(message,index) in history.messages" :key="message.messageId">
  11. <!--时间显示,类似于微信,隔5分钟不发言,才显示时间-->
  12. <view class="time-lag">
  13. {{ renderMessageDate(message, index) }}
  14. </view>
  15. <view class="message-recalled" v-if="message.recalled">
  16. <view v-if="message.recaller.id === currentUser.id" class="message-recalled-self">
  17. <view>你撤回了一条消息</view>
  18. <span v-if="message.type === 'text' && Date.now()-message.timestamp< 60 * 1000 "
  19. @click="editRecalledMessage(message.payload.text)">重新编辑</span>
  20. </view>
  21. <view v-else>{{ message.recaller.data.name }}撤回了一条消息</view>
  22. </view>
  23. <view class="message-item" v-else>
  24. <view class="message-item-checkbox">
  25. <checkbox v-show="messageSelector.visible && message.status !== 'sending'" :value="message.messageId"
  26. :checked="messageSelector.messages.includes(message)"/>
  27. </view>
  28. <view class="message-item-content" :class="{'self' : message.senderId === currentUser.id}">
  29. <view class="avatar">
  30. <!-- <image :src="message.senderId === currentUser.id? currentUser.avatar : friend.avatar"></image> -->
  31. <image src="/static/img/touxiang.png"></image>
  32. </view>
  33. <view class="content" @click.right="showActionPopup(message)" @longpress="showActionPopup(message)">
  34. <view class="message-payload">
  35. <b class="pending" v-if="message.status === 'sending'"></b>
  36. <b class="send-fail" v-if="message.status === 'fail'"></b>
  37. <view v-if="message.type === 'text'" class="text-content" v-html="renderTextMessage(message)"></view>
  38. <image v-if="message.type === 'image'"
  39. :data-url="message.payload.url"
  40. :src="message.payload.thumbnail"
  41. class="image-content"
  42. mode="heightFix"
  43. @click="showImageFullScreen"
  44. ></image>
  45. <view class="video-snapshot" v-if="message.type === 'video'" :data-url="message.payload.video.url"
  46. @click="playVideo">
  47. <image
  48. :src="message.payload.thumbnail.url"
  49. :style="{height: getImageHeight(message.payload.thumbnail.width,message.payload.thumbnail.height)+'rpx' }"
  50. mode="heightFix"
  51. ></image>
  52. <view class="video-play-icon"></view>
  53. </view>
  54. <view class="file-content" v-if="message.type === 'file'">
  55. <view class="file-info">
  56. <span class="file-name">{{ message.payload.name }}</span>
  57. <span class="file-size">{{ (message.payload.size / 1024).toFixed(2) }}KB</span>
  58. </view>
  59. <image class="file-img" src="/static/images/file-icon.png"></image>
  60. </view>
  61. <view v-if="message.type ==='audio'" class="audio-content" @click="playAudio(message)">
  62. <view class="audio-facade" :style="{width:Math.ceil(message.payload.duration)*7 + 50 + 'px'}">
  63. <view
  64. class="audio-facade-bg"
  65. :class="{'play-icon':audioPlayer.playingMessage && audioPlayer.playingMessage.messageId === message.messageId}"
  66. ></view>
  67. <view>{{Math.ceil(message.payload.duration) || 1}}<span>"</span></view>
  68. </view>
  69. </view>
  70. <view v-if="message.type === 'order'" class="order-content">
  71. <view class="order-id">订单号:{{ message.payload.id }}</view>
  72. <view class="order-body">
  73. <image :src="message.payload.url" class="order-img"></image>
  74. <view>
  75. <view class="order-name">{{ message.payload.name }}</view>
  76. <view class="order-info">
  77. <view class="order-price">{{ message.payload.price }}</view>
  78. <view class="order-count">共{{ message.payload.count }}件</view>
  79. </view>
  80. </view>
  81. </view>
  82. </view>
  83. <view v-if="message.type === 'car'" class="text-content" @click="gocar">
  84. <view class="mVin">{{message.payload.vin}}</view>
  85. <view class="mcar">{{message.payload.carModel}}</view>
  86. </view>
  87. </view>
  88. <view v-if="message.senderId === currentUser.id" :class="message.read ?'message-read':'message-unread'">
  89. <view v-if="message.status === 'success'">{{ message.read ? '已读' : '未读' }}</view>
  90. </view>
  91. </view>
  92. </view>
  93. </view>
  94. </view>
  95. </checkbox-group>
  96. </view>
  97. <view class="action-box" v-if="!videoPlayer.visible && !messageSelector.visible">
  98. <view class="action-top">
  99. <view @click="switchAudioKeyboard">
  100. <image class="more" v-if="audio.visible" src="/static/images/jianpan.png"></image>
  101. <image class="more" v-else src="/static/images/audio.png"></image>
  102. </view>
  103. <view v-if="audio.visible" class="record-input" @touchend.stop="onRecordEnd" @touchstart.stop="onRecordStart">
  104. {{ recorderManager.recording ? '松开发送' : '按住录音' }}
  105. </view>
  106. <!-- GoEasyIM最大支持3k的文本消息,如需发送长文本,需调整输入框maxlength值 -->
  107. <input v-else v-model="text" @confirm="sendTextMessage" class="consult-input" maxlength="700" placeholder="发送消息" type="text" />
  108. <view @click="switchEmojiKeyboard">
  109. <image class="more" v-if="emoji.visible" src="/static/images/jianpan.png"></image>
  110. <image class="more" v-else src="/static/images/emoji.png"></image>
  111. </view>
  112. <view>
  113. <image @click="showOtherTypesMessagePanel()" class="more" src="/static/images/more.png"/>
  114. </view>
  115. <view v-if="text" class="send-btn-box">
  116. <text class="btn" @click="sendTextMessage()">发送</text>
  117. </view>
  118. </view>
  119. <!--展示表情列表-->
  120. <view class="action-bottom action-bottom-emoji" v-if="emoji.visible">
  121. <image class="emoji-item" v-for="(emojiItem, emojiKey, index) in emoji.map" :key="index"
  122. :src="emoji.url + emojiItem" @click="chooseEmoji(emojiKey)"></image>
  123. </view>
  124. <!--其他类型消息面板-->
  125. <view v-if="otherTypesMessagePanelVisible" class="action-bottom">
  126. <view class="more-icon">
  127. <image @click="sendImageMessage()" class="operation-icon" src="/static/images/picture.png"></image>
  128. <view class="operation-title">图片</view>
  129. </view>
  130. <view class="more-icon">
  131. <image @click="sendVideoMessage()" class="operation-icon" src="/static/images/video.png"></image>
  132. <view class="operation-title">视频</view>
  133. </view>
  134. <!-- <view class="more-icon">
  135. <image @click="showOrderMessageList()" class="operation-icon" src="/static/images/order.png"></image>
  136. <view class="operation-title">订单</view>
  137. </view>
  138. <view class="more-icon">
  139. <image @click="privateCall()" class="operation-icon" src="/static/images/rtc.png"></image>
  140. <view class="operation-title">视频通话</view>
  141. </view> -->
  142. </view>
  143. </view>
  144. <view class="action-popup" @touchmove.stop.prevent v-if="actionPopup.visible">
  145. <view class="layer"></view>
  146. <view class="action-list">
  147. <view class="action-item" @click="deleteSingleMessage">删除</view>
  148. <view class="action-item" v-if="actionPopup.recallable" @click="recallMessage">撤回</view>
  149. <view class="action-item" @click="showCheckBox">多选</view>
  150. <view class="action-item" @click="hideActionPopup">取消</view>
  151. </view>
  152. </view>
  153. <view class="messageSelector-box" v-if="messageSelector.visible">
  154. <image class="messageSelector-btn" @click="deleteMultipleMessages" src="/static/images/delete.png"></image>
  155. </view>
  156. <view class="record-loading" v-if="recorderManager.recording"></view>
  157. <video v-if="videoPlayer.visible" :src="videoPlayer.url" id="videoPlayer"
  158. @fullscreenchange="onVideoFullScreenChange"></video>
  159. <view v-if="orderList.visible" class="order-list">
  160. <view class="orders-content">
  161. <view class="title">
  162. <view>请选择一个订单</view>
  163. <view class="close" @click="hideOrderMessageList">×</view>
  164. </view>
  165. <view class="orders">
  166. <view
  167. v-for="(order, index) in orderList.orders"
  168. :key="index" class="order-item"
  169. @click="sendOrderMessage(order)"
  170. >
  171. <view class="order-id">订单号:{{ order.id }}</view>
  172. <view class="order-body">
  173. <image :src="order.url" class="order-img"></image>
  174. <view class="order-name">{{ order.name }}</view>
  175. <view class="order-right">
  176. <view class="order-price">{{ order.price }}</view>
  177. <view class="order-count">共{{ order.count }}件</view>
  178. </view>
  179. </view>
  180. </view>
  181. </view>
  182. </view>
  183. </view>
  184. </view>
  185. </template>
  186. <script>
  187. import EmojiDecoder from '../lib/EmojiDecoder';
  188. import restApi from '../lib/restapi';
  189. import {formatDate} from '../lib/utils';
  190. import RecorderManager from '../lib/RecorderManager';
  191. const IMAGE_MAX_WIDTH = 200;
  192. const IMAGE_MAX_HEIGHT = 150;
  193. const recorderManager = new RecorderManager();
  194. const GoEasy = uni.$GoEasy;
  195. const GRTC = uni.$GRTC;
  196. export default {
  197. name: 'privateChat',
  198. data() {
  199. const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
  200. const emojiMap = {
  201. '[么么哒]': 'emoji_3@2x.png',
  202. '[乒乓]': 'emoji_4@2x.png',
  203. '[便便]': 'emoji_5@2x.png',
  204. '[信封]': 'emoji_6@2x.png',
  205. '[偷笑]': 'emoji_7@2x.png',
  206. '[傲慢]': 'emoji_8@2x.png'
  207. };
  208. return {
  209. //聊天文本框
  210. text: '',
  211. friend: null,
  212. to: {},// 作为createMessage的参数
  213. currentUser: null,
  214. //定义表情列表
  215. emoji: {
  216. url: emojiUrl,
  217. map: emojiMap,
  218. visible: false,
  219. decoder: new EmojiDecoder(emojiUrl, emojiMap),
  220. },
  221. //是否展示‘其他消息类型面板’
  222. otherTypesMessagePanelVisible: false,
  223. orderList: {
  224. orders: [],
  225. visible: false
  226. },
  227. history: {
  228. messages: [],
  229. allLoaded: false,
  230. loading: false
  231. },
  232. recorderManager: recorderManager,
  233. audio: {
  234. //录音按钮展示
  235. visible: false
  236. },
  237. audioPlayer: {
  238. innerAudioContext: null,
  239. playingMessage: null,
  240. },
  241. videoPlayer: {
  242. visible: false,
  243. url: '',
  244. context: null
  245. },
  246. // 展示消息删除弹出框
  247. actionPopup: {
  248. visible: false,
  249. message: null,
  250. recallable: false,
  251. },
  252. // 消息选择
  253. messageSelector: {
  254. visible: false,
  255. messages: []
  256. }
  257. }
  258. },
  259. onLoad(options) {
  260. //聊天对象
  261. let id = options.to;
  262. this.friend = uni.getStorageSync("friend");
  263. //this.friend = restApi.findUserById(id);
  264. //this.currentUser = uni.$currentUser;
  265. this.currentUser = uni.getStorageSync("currentUser");
  266. this.to = {
  267. id: this.friend.id,
  268. type: GoEasy.IM_SCENE.PRIVATE,
  269. data: {
  270. name: this.friend.supplierName,
  271. avatar: '/static/images/Avatar-1.png'
  272. }
  273. };
  274. this.initGoEasyListeners();
  275. // 语音播放器
  276. this.initialAudioPlayer();
  277. // 录音监听器
  278. this.initRecorderListeners();
  279. },
  280. onShow() {
  281. this.otherTypesMessagePanelVisible = false;
  282. this.emoji.visible = false;
  283. },
  284. onReady() {
  285. this.loadHistoryMessage(true);
  286. this.videoPlayer.context = uni.createVideoContext('videoPlayer', this);
  287. // https://uniapp.dcloud.io/api/ui/navigationbar?id=setnavigationbartitle
  288. uni.setNavigationBarTitle({ title: this.friend.supplierName });
  289. },
  290. onPullDownRefresh(e) {
  291. this.loadHistoryMessage(false);
  292. },
  293. onUnload() {
  294. //退出聊天页面之前,清空监听器
  295. GoEasy.im.off(GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this.onMessageReceived);
  296. GoEasy.im.off(GoEasy.IM_EVENT.MESSAGE_DELETED, this.onMessageDeleted);
  297. GoEasy.im.off(GoEasy.IM_EVENT.HISTORY_EXPIRED, this.onHistoryExpired);
  298. },
  299. methods: {
  300. //渲染文本消息,如果包含表情,替换为图片
  301. //todo:本不需要该方法,可以在标签里完成,但小程序有兼容性问题,被迫这样实现
  302. renderTextMessage(message) {
  303. return '<span>' + this.emoji.decoder.decode(message.payload.text) + '</span>'
  304. },
  305. //像微信那样显示时间,如果有几分钟没发消息了,才显示时间
  306. //todo:本不需要该方法,可以在标签里完成,但小程序有兼容性问题,被迫这样实现
  307. renderMessageDate(message, index) {
  308. if (index === 0) {
  309. return formatDate(message.timestamp)
  310. } else {
  311. if (message.timestamp - this.history.messages[index - 1].timestamp > 5 * 60 * 1000) {
  312. return formatDate(message.timestamp)
  313. }
  314. }
  315. return '';
  316. },
  317. initGoEasyListeners() {
  318. // 监听私聊消息
  319. GoEasy.im.on(GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this.onMessageReceived);
  320. //监听消息删除
  321. GoEasy.im.on(GoEasy.IM_EVENT.MESSAGE_DELETED, this.onMessageDeleted);
  322. // 监听断网重连
  323. GoEasy.im.on(GoEasy.IM_EVENT.HISTORY_EXPIRED, this.onHistoryExpired);
  324. },
  325. onMessageReceived (message) {
  326. let senderId = message.senderId;
  327. let receiverId = message.receiverId;
  328. let friendId = this.currentUser.id === senderId ? receiverId : senderId;
  329. if (friendId === this.friend.id) {
  330. this.history.messages.push(message);
  331. //聊天时,收到消息标记为已读
  332. this.markPrivateMessageAsRead();
  333. //收到新消息,是滚动到最底部
  334. this.scrollToBottom();
  335. }
  336. },
  337. onMessageDeleted (deletedMessages) {
  338. deletedMessages.forEach(message => {
  339. let senderId = message.senderId;
  340. let receiverId = message.receiverId;
  341. let friendId = this.currentUser.id === senderId ? receiverId : senderId;
  342. if (friendId === this.friend.id) {
  343. let index = this.history.messages.indexOf(message);
  344. if (index > -1) {
  345. this.history.messages.splice(index, 1);
  346. }
  347. }
  348. });
  349. },
  350. onHistoryExpired() {
  351. this.history.messages = [];
  352. this.loadHistoryMessage(true);
  353. },
  354. initialAudioPlayer () {
  355. this.audioPlayer.innerAudioContext = uni.createInnerAudioContext();
  356. this.audioPlayer.innerAudioContext.onEnded(() => {
  357. this.audioPlayer.playingMessage = null;
  358. });
  359. this.audioPlayer.innerAudioContext.onStop(() => {
  360. this.audioPlayer.playingMessage = null;
  361. });
  362. },
  363. initRecorderListeners() {
  364. recorderManager.onRecordComplete((file, duration) => {
  365. if (duration < 1000) {
  366. uni.showToast({
  367. icon: 'none',
  368. title: '录音时间太短',
  369. duration: 500
  370. });
  371. return;
  372. }
  373. GoEasy.im.createAudioMessage({
  374. to: this.to,
  375. file: file,
  376. notification: {
  377. title: this.currentUser.name + '发来一段语音',
  378. body: '[语音消息]', // 字段最长 50 字符
  379. sound: 'message',
  380. badge: '+1'
  381. },
  382. onProgress: function (progress) {
  383. console.log(progress)
  384. },
  385. onSuccess: (message) => {
  386. this.sendMessage(message);
  387. },
  388. onFailed: (e) => {
  389. console.log('error :', e);
  390. }
  391. });
  392. });
  393. },
  394. /**
  395. * 核心就是设置高度,产生明确占位
  396. *
  397. * 小 (宽度和高度都小于预设尺寸)
  398. * 设高=原始高度
  399. * 宽 (宽度>高度)
  400. * 高度= 根据宽度等比缩放
  401. * 窄 (宽度<高度)或方(宽度=高度)
  402. * 设高=MAX height
  403. *
  404. * @param width,height
  405. * @returns number
  406. */
  407. getImageHeight(width, height) {
  408. if (width < IMAGE_MAX_WIDTH && height < IMAGE_MAX_HEIGHT) {
  409. return height * 2;
  410. } else if (width > height) {
  411. return (IMAGE_MAX_WIDTH / width * height) * 2;
  412. } else if (width === height || width < height) {
  413. return IMAGE_MAX_HEIGHT * 2;
  414. }
  415. },
  416. sendMessage(message) {
  417. this.history.messages.push(message);
  418. this.scrollToBottom();
  419. GoEasy.im.sendMessage({
  420. message: message,
  421. onSuccess: function () {
  422. console.log('发送成功.', message);
  423. },
  424. onFailed: function (error) {
  425. if (error.code === 507) {
  426. console.log('发送语音/图片/视频/文件失败,没有配置OSS存储,详情参考:https://docs.goeasy.io/2.x/im/message/media/alioss');
  427. } else {
  428. console.log('发送失败:', error);
  429. }
  430. }
  431. });
  432. },
  433. sendTextMessage() {
  434. if (this.text.trim() !== '') {
  435. let body = this.text;
  436. if (this.text.length >= 50) {
  437. body = this.text.substring(0, 30) + '...';
  438. }
  439. GoEasy.im.createTextMessage({
  440. text: this.text,
  441. to: this.to,
  442. notification: {
  443. title: this.currentUser.name + '发来一段文字',
  444. body: body,
  445. sound: 'message',
  446. badge: '+1'
  447. },
  448. onSuccess: (message) => {
  449. this.sendMessage(message);
  450. },
  451. onFailed: (e) => {
  452. console.log('error :', e);
  453. }
  454. });
  455. }
  456. this.text = '';
  457. },
  458. sendVideoMessage() {
  459. uni.chooseVideo({
  460. success: (res) => {
  461. GoEasy.im.createVideoMessage({
  462. to: this.to,
  463. file: res,
  464. notification: {
  465. title: this.currentUser.name + '发来一个视频',
  466. body: '[视频消息]', // 字段最长 50 字符
  467. sound: 'message',
  468. badge: '+1'
  469. },
  470. onProgress: function (progress) {
  471. console.log(progress)
  472. },
  473. onSuccess: (message) => {
  474. this.otherTypesMessagePanelVisible = false;
  475. this.sendMessage(message);
  476. },
  477. onFailed: (e) => {
  478. console.log('error :', e);
  479. }
  480. });
  481. }
  482. })
  483. },
  484. sendImageMessage() {
  485. uni.chooseImage({
  486. count: 9,
  487. success: (res) => {
  488. res.tempFiles.forEach(file => {
  489. GoEasy.im.createImageMessage({
  490. to: this.to,
  491. file: file,
  492. notification: {
  493. title: this.currentUser.name + '发来一张图片',
  494. body: '[图片消息]', // 字段最长 50 字符
  495. sound: 'message',
  496. badge: '+1'
  497. },
  498. onProgress: function (progress) {
  499. console.log(progress)
  500. },
  501. onSuccess: (message) => {
  502. this.otherTypesMessagePanelVisible = false;
  503. this.sendMessage(message);
  504. },
  505. onFailed: (e) => {
  506. console.log('error :', e);
  507. }
  508. });
  509. })
  510. }
  511. });
  512. },
  513. sendOrderMessage(order) {
  514. //GoEasyIM自定义消息,实现订单发送
  515. GoEasy.im.createCustomMessage({
  516. type: 'order',
  517. payload: order,
  518. to: this.to,
  519. notification: {
  520. title: this.currentUser.name + '发来一个订单',
  521. body: '[订单消息]',
  522. sound: 'message',
  523. badge: '+1'
  524. },
  525. onSuccess: (message) => {
  526. this.otherTypesMessagePanelVisible = false;
  527. this.sendMessage(message);
  528. },
  529. onFailed: (e) => {
  530. console.log('error :', e);
  531. }
  532. });
  533. this.orderList.visible = false;
  534. },
  535. showActionPopup(message) {
  536. const MAX_RECALLABLE_TIME = 3 * 60 * 1000; //3分钟以内的消息才可以撤回
  537. this.messageSelector.messages = [message];
  538. if ((Date.now() - message.timestamp) < MAX_RECALLABLE_TIME && message.senderId === this.currentUser.id && message.status === 'success') {
  539. this.actionPopup.recallable = true;
  540. } else {
  541. this.actionPopup.recallable = false;
  542. }
  543. this.actionPopup.visible = true;
  544. },
  545. hideActionPopup () {
  546. this.actionPopup.visible = false;
  547. this.actionPopup.message = null;
  548. },
  549. deleteSingleMessage() {
  550. uni.showModal({
  551. content: '确认删除?',
  552. success: (res) => {
  553. this.actionPopup.visible = false;
  554. if (res.confirm) {
  555. this.deleteMessage();
  556. }
  557. },
  558. })
  559. },
  560. deleteMultipleMessages() {
  561. if (this.messageSelector.messages.length > 0) {
  562. uni.showModal({
  563. content: '确认删除?',
  564. success: (res) => {
  565. this.messageSelector.visible = false;
  566. if (res.confirm) {
  567. this.deleteMessage();
  568. }
  569. },
  570. })
  571. }
  572. },
  573. deleteMessage() {
  574. GoEasy.im.deleteMessage({
  575. messages: this.messageSelector.messages,
  576. onSuccess: (result) => {
  577. this.messageSelector.messages.forEach(message => {
  578. let index = this.history.messages.indexOf(message);
  579. if (index > -1) {
  580. this.history.messages.splice(index, 1);
  581. }
  582. });
  583. this.messageSelector.messages = [];
  584. },
  585. onFailed: (error) => {
  586. console.log('error:', error);
  587. }
  588. });
  589. },
  590. recallMessage() {
  591. this.actionPopup.visible = false;
  592. GoEasy.im.recallMessage({
  593. messages: this.messageSelector.messages,
  594. onSuccess: () => {
  595. console.log('撤回成功');
  596. },
  597. onFailed: (error) => {
  598. console.log('撤回失败,error:', error);
  599. }
  600. });
  601. },
  602. editRecalledMessage(text) {
  603. if (this.audio.visible) {
  604. this.audio.visible = false;
  605. }
  606. this.text = text;
  607. },
  608. showCheckBox() {
  609. this.messageSelector.messages = [];
  610. this.messageSelector.visible = true;
  611. this.actionPopup.visible = false;
  612. },
  613. selectMessages(e) {
  614. const selectedMessageIds = e.detail.value;
  615. let selectedMessages = [];
  616. this.history.messages.forEach(message => {
  617. if (selectedMessageIds.includes(message.messageId)) {
  618. selectedMessages.push(message);
  619. }
  620. })
  621. this.messageSelector.messages = selectedMessages;
  622. },
  623. loadHistoryMessage(scrollToBottom) {//历史消息
  624. this.history.loading = true;
  625. let lastMessageTimeStamp = null;
  626. let lastMessage = this.history.messages[0];
  627. if (lastMessage) {
  628. lastMessageTimeStamp = lastMessage.timestamp;
  629. }
  630. GoEasy.im.history({
  631. id: this.friend.id,
  632. type: GoEasy.IM_SCENE.PRIVATE,
  633. lastTimestamp: lastMessageTimeStamp,
  634. limit: 10,
  635. onSuccess: (result) => {
  636. uni.stopPullDownRefresh();
  637. this.history.loading = false;
  638. let messages = result.content;
  639. if (messages.length === 0) {
  640. this.history.allLoaded = true;
  641. } else {
  642. if (lastMessageTimeStamp) {
  643. this.history.messages = messages.concat(this.history.messages);
  644. } else {
  645. this.history.messages = messages;
  646. }
  647. if (messages.length < 10) {
  648. this.history.allLoaded = true;
  649. }
  650. if (scrollToBottom) {
  651. this.scrollToBottom();
  652. //收到的消息设置为已读
  653. this.markPrivateMessageAsRead();
  654. }
  655. }
  656. },
  657. onFailed: (error) => {
  658. //获取失败
  659. console.log('获取历史消息失败:', error);
  660. uni.stopPullDownRefresh();
  661. this.history.loading = false;
  662. }
  663. });
  664. },
  665. //语音录制按钮和键盘输入的切换
  666. switchAudioKeyboard() {
  667. if (!this.audio.visible) {
  668. recorderManager.authorize().then(() => {
  669. console.log('录音权限获取成功');
  670. this.audio.visible = true;
  671. }).catch((err) => {
  672. console.log('err:', err)
  673. uni.showModal({
  674. title: '获取录音权限失败',
  675. content: '请先打开麦克风权限'
  676. });
  677. });
  678. } else {
  679. this.audio.visible = false;
  680. }
  681. },
  682. onRecordStart() {
  683. recorderManager.start();
  684. },
  685. onRecordEnd() {
  686. recorderManager.stop();
  687. },
  688. showImageFullScreen(e) {
  689. let imagesUrl = [e.currentTarget.dataset.url];
  690. uni.previewImage({
  691. urls: imagesUrl
  692. });
  693. },
  694. playVideo(e) {
  695. this.videoPlayer.visible = true;
  696. this.videoPlayer.url = e.currentTarget.dataset.url;
  697. this.$nextTick(() => {
  698. this.videoPlayer.context.requestFullScreen({
  699. direction: 0
  700. });
  701. this.videoPlayer.context.play();
  702. });
  703. },
  704. playAudio (audioMessage) {
  705. let playingMessage = this.audioPlayer.playingMessage;
  706. if (playingMessage) {
  707. this.audioPlayer.innerAudioContext.stop();
  708. // 如果点击的消息正在播放,就认为是停止播放操作
  709. if (playingMessage === audioMessage) {
  710. return;
  711. }
  712. }
  713. this.audioPlayer.playingMessage = audioMessage;
  714. this.audioPlayer.innerAudioContext.src = audioMessage.payload.url;
  715. this.audioPlayer.innerAudioContext.play();
  716. },
  717. onVideoFullScreenChange(e) {
  718. //当退出全屏播放时,隐藏播放器
  719. if (this.videoPlayer.visible && !e.detail.fullScreen) {
  720. this.videoPlayer.visible = false;
  721. this.videoPlayer.context.stop();
  722. }
  723. },
  724. messageInputFocusin() {
  725. this.otherTypesMessagePanelVisible = false;
  726. this.emoji.visible = false;
  727. },
  728. switchEmojiKeyboard() {
  729. this.emoji.visible = !this.emoji.visible;
  730. this.otherTypesMessagePanelVisible = false;
  731. },
  732. showOtherTypesMessagePanel() {
  733. this.otherTypesMessagePanelVisible = !this.otherTypesMessagePanelVisible;
  734. this.emoji.visible = false;
  735. },
  736. chooseEmoji(emojiKey) {
  737. this.text += emojiKey;
  738. },
  739. showOrderMessageList() {
  740. this.orderList.orders = restApi.getOrderList();
  741. this.orderList.visible = true;
  742. },
  743. hideOrderMessageList() {
  744. this.orderList.visible = false;
  745. },
  746. privateCall() {
  747. uni.showActionSheet({
  748. itemList: ['视频通话', '音频通话'],
  749. success: (res) => {
  750. const mediaType = res.tapIndex === 0 ? 1 : 0;
  751. const notificationBody = res.tapIndex === 0 ? '邀请你视频通话' : '邀请你语音通话';
  752. GRTC.call({
  753. calleeId: this.friend.id,
  754. mediaType: mediaType,
  755. notification: {
  756. title: this.currentUser.name,
  757. body: notificationBody,
  758. sound: 'ring',
  759. badge: '+1'
  760. },
  761. }).then(() => {
  762. uni.navigateTo({
  763. url: `./rtc/private/dial`,
  764. })
  765. }).catch((error)=>{
  766. console.log("呼叫失败:", error);
  767. uni.showToast({
  768. icon: "error",
  769. title: "呼叫失败:" + error,
  770. duration: 2000
  771. })
  772. })
  773. },
  774. fail: (res) => {
  775. console.log(res.errMsg);
  776. }
  777. });
  778. },
  779. scrollToBottom() {
  780. this.$nextTick(() => {
  781. uni.pageScrollTo({
  782. scrollTop: 2000000,
  783. duration: 0
  784. });
  785. });
  786. },
  787. markPrivateMessageAsRead() {
  788. GoEasy.im.markMessageAsRead({
  789. id: this.to.id,
  790. type: this.to.type,
  791. onSuccess: function () {
  792. console.log('标记私聊已读成功');
  793. },
  794. onFailed: function (error) {
  795. console.log("标记私聊已读失败", error);
  796. }
  797. });
  798. }
  799. }
  800. }
  801. </script>
  802. <style>
  803. @import url('../static/style/chatInterface.css');
  804. </style>