qunfa.vue 35 KB

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