privateChat.vue 31 KB

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