qunfa.vue 32 KB

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