privateChatWeb.vue 35 KB

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