qunfa.vue 34 KB

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