groupChat.vue 29 KB

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