qunfa.vue 32 KB

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