qunfa.vue 37 KB

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