qunfa.vue 37 KB

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