|
@@ -0,0 +1,837 @@
|
|
|
+<template>
|
|
|
+ <view class="chatInterface" @contextmenu.prevent="">
|
|
|
+ <view class="scroll-view">
|
|
|
+ <image v-if="history.loading" class="history-loaded" src="/static/images/loading.svg"/>
|
|
|
+ <view v-else :class="history.allLoaded ? 'history-loaded':'load'" @click="loadHistoryMessage(false)">
|
|
|
+ <view>{{ history.allLoaded ? '已经没有更多的历史消息' : '点击获取历史消息' }}</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <checkbox-group @change="selectMessages">
|
|
|
+ <!--消息记录-->
|
|
|
+ <view v-for="(message,index) in history.messages" :key="message.messageId">
|
|
|
+ <!--时间显示,类似于微信,隔5分钟不发言,才显示时间-->
|
|
|
+ <view class="time-lag">
|
|
|
+ {{ renderMessageDate(message, index) }}
|
|
|
+ </view>
|
|
|
+ <view class="message-recalled" v-if="message.recalled">
|
|
|
+ <view v-if="message.recaller.id === currentUser.userId" class="message-recalled-self">
|
|
|
+ <view>你撤回了一条消息</view>
|
|
|
+ <span v-if="message.type === 'text' && Date.now()-message.timestamp< 60 * 1000 "
|
|
|
+ @click="editRecalledMessage(message.payload.text)">重新编辑</span>
|
|
|
+ </view>
|
|
|
+ <view v-else>{{ message.recaller.data.name }}撤回了一条消息</view>
|
|
|
+ </view>
|
|
|
+ <view class="message-item" v-else>
|
|
|
+ <view class="message-item-checkbox">
|
|
|
+ <checkbox v-show="messageSelector.visible && message.status !== 'sending'" :value="message.messageId"
|
|
|
+ :checked="messageSelector.messages.includes(message)"/>
|
|
|
+ </view>
|
|
|
+ <view class="message-item-content" :class="{'self' : message.senderId === currentUser.userId}">
|
|
|
+ <view class="avatar">
|
|
|
+ <!-- <image :src="message.senderId === currentUser.id? currentUser.avatar : friend.avatar"></image> -->
|
|
|
+ <image src="/static/img/touxiang.png"></image>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="content" @click.right="showActionPopup(message)" @longpress="showActionPopup(message)">
|
|
|
+ <view class="message-payload">
|
|
|
+ <b class="pending" v-if="message.status === 'sending'"></b>
|
|
|
+ <b class="send-fail" v-if="message.status === 'fail'"></b>
|
|
|
+ <view v-if="message.type === 'text'" class="text-content" v-html="renderTextMessage(message)"></view>
|
|
|
+ <image v-if="message.type === 'image'"
|
|
|
+ :data-url="message.payload.url"
|
|
|
+ :src="message.payload.thumbnail"
|
|
|
+ class="image-content"
|
|
|
+ mode="heightFix"
|
|
|
+ @click="showImageFullScreen"
|
|
|
+ ></image>
|
|
|
+ <view class="video-snapshot" v-if="message.type === 'video'" :data-url="message.payload.video.url"
|
|
|
+ @click="playVideo">
|
|
|
+ <image
|
|
|
+ :src="message.payload.thumbnail.url"
|
|
|
+ :style="{height: getImageHeight(message.payload.thumbnail.width,message.payload.thumbnail.height)+'rpx' }"
|
|
|
+ mode="heightFix"
|
|
|
+ ></image>
|
|
|
+ <view class="video-play-icon"></view>
|
|
|
+ </view>
|
|
|
+ <view class="file-content" v-if="message.type === 'file'">
|
|
|
+ <view class="file-info">
|
|
|
+ <span class="file-name">{{ message.payload.name }}</span>
|
|
|
+ <span class="file-size">{{ (message.payload.size / 1024).toFixed(2) }}KB</span>
|
|
|
+ </view>
|
|
|
+ <image class="file-img" src="/static/images/file-icon.png"></image>
|
|
|
+ </view>
|
|
|
+ <view v-if="message.type ==='audio'" class="audio-content" @click="playAudio(message)">
|
|
|
+ <view class="audio-facade" :style="{width:Math.ceil(message.payload.duration)*7 + 50 + 'px'}">
|
|
|
+ <view
|
|
|
+ class="audio-facade-bg"
|
|
|
+ :class="{'play-icon':audioPlayer.playingMessage && audioPlayer.playingMessage.messageId === message.messageId}"
|
|
|
+ ></view>
|
|
|
+ <view>{{Math.ceil(message.payload.duration) || 1}}<span>"</span></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="message.type === 'order'" class="order-content">
|
|
|
+ <view class="order-id">订单号:{{ message.payload.id }}</view>
|
|
|
+ <view class="order-body">
|
|
|
+ <image :src="message.payload.url" class="order-img"></image>
|
|
|
+ <view>
|
|
|
+ <view class="order-name">{{ message.payload.name }}</view>
|
|
|
+ <view class="order-info">
|
|
|
+ <view class="order-price">{{ message.payload.price }}</view>
|
|
|
+ <view class="order-count">共{{ message.payload.count }}件</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="message.type === 'car'" class="text-content" @click="gocar">
|
|
|
+ <view class="mVin">{{message.payload.vin}}</view>
|
|
|
+ <view class="mcar">{{message.payload.carModel}}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="message.senderId === currentUser.userId" :class="message.read ?'message-read':'message-unread'">
|
|
|
+ <view v-if="message.status === 'success'">{{ message.read ? '已读' : '未读' }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </checkbox-group>
|
|
|
+ </view>
|
|
|
+ <view class="action-box" v-if="!videoPlayer.visible && !messageSelector.visible">
|
|
|
+ <view class="action-top">
|
|
|
+ <view @click="switchAudioKeyboard">
|
|
|
+ <image class="more" v-if="audio.visible" src="/static/images/jianpan.png"></image>
|
|
|
+ <image class="more" v-else src="/static/images/audio.png"></image>
|
|
|
+ </view>
|
|
|
+ <view v-if="audio.visible" class="record-input" @touchend.stop="onRecordEnd" @touchstart.stop="onRecordStart">
|
|
|
+ {{ recorderManager.recording ? '松开发送' : '按住录音' }}
|
|
|
+ </view>
|
|
|
+ <!-- GoEasyIM最大支持3k的文本消息,如需发送长文本,需调整输入框maxlength值 -->
|
|
|
+ <input v-else v-model="text" @confirm="sendTextMessage" class="consult-input" maxlength="700" placeholder="发送消息" type="text" />
|
|
|
+ <view @click="switchEmojiKeyboard">
|
|
|
+ <image class="more" v-if="emoji.visible" src="/static/images/jianpan.png"></image>
|
|
|
+ <image class="more" v-else src="/static/images/emoji.png"></image>
|
|
|
+ </view>
|
|
|
+ <view>
|
|
|
+ <image @click="showOtherTypesMessagePanel()" class="more" src="/static/images/more.png"/>
|
|
|
+ </view>
|
|
|
+ <view v-if="text" class="send-btn-box">
|
|
|
+ <text class="btn" @click="sendTextMessage()">发送</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!--展示表情列表-->
|
|
|
+ <view class="action-bottom action-bottom-emoji" v-if="emoji.visible">
|
|
|
+ <image class="emoji-item" v-for="(emojiItem, emojiKey, index) in emoji.map" :key="index"
|
|
|
+ :src="emoji.url + emojiItem" @click="chooseEmoji(emojiKey)"></image>
|
|
|
+ </view>
|
|
|
+ <!--其他类型消息面板-->
|
|
|
+ <view v-if="otherTypesMessagePanelVisible" class="action-bottom">
|
|
|
+ <view class="more-icon">
|
|
|
+ <image @click="sendImageMessage()" class="operation-icon" src="/static/images/picture.png"></image>
|
|
|
+ <view class="operation-title">图片</view>
|
|
|
+ </view>
|
|
|
+ <view class="more-icon">
|
|
|
+ <image @click="sendVideoMessage()" class="operation-icon" src="/static/images/video.png"></image>
|
|
|
+ <view class="operation-title">视频</view>
|
|
|
+ </view>
|
|
|
+ <!-- <view class="more-icon">
|
|
|
+ <image @click="showOrderMessageList()" class="operation-icon" src="/static/images/order.png"></image>
|
|
|
+ <view class="operation-title">订单</view>
|
|
|
+ </view>
|
|
|
+ <view class="more-icon">
|
|
|
+ <image @click="privateCall()" class="operation-icon" src="/static/images/rtc.png"></image>
|
|
|
+ <view class="operation-title">视频通话</view>
|
|
|
+ </view> -->
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="action-popup" @touchmove.stop.prevent v-if="actionPopup.visible">
|
|
|
+ <view class="layer"></view>
|
|
|
+ <view class="action-list">
|
|
|
+ <view class="action-item" @click="deleteSingleMessage">删除</view>
|
|
|
+ <view class="action-item" v-if="actionPopup.recallable" @click="recallMessage">撤回</view>
|
|
|
+ <view class="action-item" @click="showCheckBox">多选</view>
|
|
|
+ <view class="action-item" @click="hideActionPopup">取消</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="messageSelector-box" v-if="messageSelector.visible">
|
|
|
+ <image class="messageSelector-btn" @click="deleteMultipleMessages" src="/static/images/delete.png"></image>
|
|
|
+ </view>
|
|
|
+ <view class="record-loading" v-if="recorderManager.recording"></view>
|
|
|
+ <video v-if="videoPlayer.visible" :src="videoPlayer.url" id="videoPlayer"
|
|
|
+ @fullscreenchange="onVideoFullScreenChange"></video>
|
|
|
+ <view v-if="orderList.visible" class="order-list">
|
|
|
+ <view class="orders-content">
|
|
|
+ <view class="title">
|
|
|
+ <view>请选择一个订单</view>
|
|
|
+ <view class="close" @click="hideOrderMessageList">×</view>
|
|
|
+ </view>
|
|
|
+ <view class="orders">
|
|
|
+ <view
|
|
|
+ v-for="(order, index) in orderList.orders"
|
|
|
+ :key="index" class="order-item"
|
|
|
+ @click="sendOrderMessage(order)"
|
|
|
+ >
|
|
|
+ <view class="order-id">订单号:{{ order.id }}</view>
|
|
|
+ <view class="order-body">
|
|
|
+ <image :src="order.url" class="order-img"></image>
|
|
|
+ <view class="order-name">{{ order.name }}</view>
|
|
|
+ <view class="order-right">
|
|
|
+ <view class="order-price">{{ order.price }}</view>
|
|
|
+ <view class="order-count">共{{ order.count }}件</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="videoBox" v-if="videoShow" @click="videoShow=false">
|
|
|
+
|
|
|
+ <view style="width: 100%;" @click.stop="">
|
|
|
+ <video :src="videoPlayer.url" id="videoPlayer"
|
|
|
+ ></video>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+ import EmojiDecoder from '../lib/EmojiDecoder';
|
|
|
+ import restApi from '../lib/restapi';
|
|
|
+ import {formatDate} from '../lib/utils';
|
|
|
+ import RecorderManager from '../lib/RecorderManager';
|
|
|
+
|
|
|
+ const IMAGE_MAX_WIDTH = 200;
|
|
|
+ const IMAGE_MAX_HEIGHT = 150;
|
|
|
+ const recorderManager = new RecorderManager();
|
|
|
+ const GoEasy = uni.$GoEasy;
|
|
|
+ const GRTC = uni.$GRTC;
|
|
|
+ export default {
|
|
|
+ name: 'privateChat',
|
|
|
+ data() {
|
|
|
+ const emojiUrl = 'https://imgcache.qq.com/open/qcloud/tim/assets/emoji/';
|
|
|
+ const emojiMap = {
|
|
|
+ '[么么哒]': 'emoji_3@2x.png',
|
|
|
+ '[乒乓]': 'emoji_4@2x.png',
|
|
|
+ '[便便]': 'emoji_5@2x.png',
|
|
|
+ '[信封]': 'emoji_6@2x.png',
|
|
|
+ '[偷笑]': 'emoji_7@2x.png',
|
|
|
+ '[傲慢]': 'emoji_8@2x.png'
|
|
|
+ };
|
|
|
+ return {
|
|
|
+ //聊天文本框
|
|
|
+ text: '',
|
|
|
+ friend: null,
|
|
|
+ to: {},// 作为createMessage的参数
|
|
|
+ currentUser: null,
|
|
|
+
|
|
|
+ //定义表情列表
|
|
|
+ emoji: {
|
|
|
+ url: emojiUrl,
|
|
|
+ map: emojiMap,
|
|
|
+ visible: false,
|
|
|
+ decoder: new EmojiDecoder(emojiUrl, emojiMap),
|
|
|
+ },
|
|
|
+ //是否展示‘其他消息类型面板’
|
|
|
+ otherTypesMessagePanelVisible: false,
|
|
|
+ orderList: {
|
|
|
+ orders: [],
|
|
|
+ visible: false
|
|
|
+ },
|
|
|
+ history: {
|
|
|
+ messages: [],
|
|
|
+ allLoaded: false,
|
|
|
+ loading: false
|
|
|
+ },
|
|
|
+ recorderManager: recorderManager,
|
|
|
+ audio: {
|
|
|
+ //录音按钮展示
|
|
|
+ visible: false
|
|
|
+ },
|
|
|
+ audioPlayer: {
|
|
|
+ innerAudioContext: null,
|
|
|
+ playingMessage: null,
|
|
|
+ },
|
|
|
+ videoPlayer: {
|
|
|
+ visible: false,
|
|
|
+ url: '',
|
|
|
+ context: null
|
|
|
+ },
|
|
|
+ // 展示消息删除弹出框
|
|
|
+ actionPopup: {
|
|
|
+ visible: false,
|
|
|
+ message: null,
|
|
|
+ recallable: false,
|
|
|
+ },
|
|
|
+ // 消息选择
|
|
|
+ messageSelector: {
|
|
|
+ visible: false,
|
|
|
+ messages: []
|
|
|
+ },
|
|
|
+ videoShow:false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(options) {
|
|
|
+ //聊天对象
|
|
|
+ let id = options.to;
|
|
|
+ this.friend = uni.getStorageSync("friend");
|
|
|
+ //this.friend = restApi.findUserById(id);
|
|
|
+ //this.currentUser = uni.$currentUser;
|
|
|
+ this.currentUser = uni.getStorageSync("loginInfo");
|
|
|
+
|
|
|
+ this.to = {
|
|
|
+ id: this.friend.id,
|
|
|
+ type: GoEasy.IM_SCENE.PRIVATE,
|
|
|
+ data: {
|
|
|
+ name: this.friend.supplierName,
|
|
|
+ avatar: '/static/images/Avatar-1.png'
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ this.initGoEasyListeners();
|
|
|
+ // 语音播放器
|
|
|
+ this.initialAudioPlayer();
|
|
|
+ // 录音监听器
|
|
|
+ this.initRecorderListeners();
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ this.otherTypesMessagePanelVisible = false;
|
|
|
+ this.emoji.visible = false;
|
|
|
+ },
|
|
|
+ onReady() {
|
|
|
+ this.loadHistoryMessage(true);
|
|
|
+ this.videoPlayer.context = uni.createVideoContext('videoPlayer', this);
|
|
|
+ // https://uniapp.dcloud.io/api/ui/navigationbar?id=setnavigationbartitle
|
|
|
+ uni.setNavigationBarTitle({ title: this.friend.supplierName });
|
|
|
+ },
|
|
|
+ onPullDownRefresh(e) {
|
|
|
+ this.loadHistoryMessage(false);
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ //退出聊天页面之前,清空监听器
|
|
|
+ GoEasy.im.off(GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this.onMessageReceived);
|
|
|
+ GoEasy.im.off(GoEasy.IM_EVENT.MESSAGE_DELETED, this.onMessageDeleted);
|
|
|
+ GoEasy.im.off(GoEasy.IM_EVENT.HISTORY_EXPIRED, this.onHistoryExpired);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ //渲染文本消息,如果包含表情,替换为图片
|
|
|
+ //todo:本不需要该方法,可以在标签里完成,但小程序有兼容性问题,被迫这样实现
|
|
|
+ renderTextMessage(message) {
|
|
|
+ return '<span>' + this.emoji.decoder.decode(message.payload.text) + '</span>'
|
|
|
+ },
|
|
|
+ //像微信那样显示时间,如果有几分钟没发消息了,才显示时间
|
|
|
+ //todo:本不需要该方法,可以在标签里完成,但小程序有兼容性问题,被迫这样实现
|
|
|
+ renderMessageDate(message, index) {
|
|
|
+ if (index === 0) {
|
|
|
+ return formatDate(message.timestamp)
|
|
|
+ } else {
|
|
|
+ if (message.timestamp - this.history.messages[index - 1].timestamp > 5 * 60 * 1000) {
|
|
|
+ return formatDate(message.timestamp)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ },
|
|
|
+ initGoEasyListeners() {
|
|
|
+ // 监听私聊消息
|
|
|
+ GoEasy.im.on(GoEasy.IM_EVENT.PRIVATE_MESSAGE_RECEIVED, this.onMessageReceived);
|
|
|
+ //监听消息删除
|
|
|
+ GoEasy.im.on(GoEasy.IM_EVENT.MESSAGE_DELETED, this.onMessageDeleted);
|
|
|
+ // 监听断网重连
|
|
|
+ GoEasy.im.on(GoEasy.IM_EVENT.HISTORY_EXPIRED, this.onHistoryExpired);
|
|
|
+ },
|
|
|
+ onMessageReceived (message) {
|
|
|
+ let senderId = message.senderId;
|
|
|
+ let receiverId = message.receiverId;
|
|
|
+ let friendId = this.currentUser.userId === senderId ? receiverId : senderId;
|
|
|
+ if (friendId === this.friend.id) {
|
|
|
+ this.history.messages.push(message);
|
|
|
+ //聊天时,收到消息标记为已读
|
|
|
+ this.markPrivateMessageAsRead();
|
|
|
+ //收到新消息,是滚动到最底部
|
|
|
+ this.scrollToBottom();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onMessageDeleted (deletedMessages) {
|
|
|
+ deletedMessages.forEach(message => {
|
|
|
+ let senderId = message.senderId;
|
|
|
+ let receiverId = message.receiverId;
|
|
|
+ let friendId = this.currentUser.userId === senderId ? receiverId : senderId;
|
|
|
+ if (friendId === this.friend.id) {
|
|
|
+ let index = this.history.messages.indexOf(message);
|
|
|
+ if (index > -1) {
|
|
|
+ this.history.messages.splice(index, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onHistoryExpired() {
|
|
|
+ this.history.messages = [];
|
|
|
+ this.loadHistoryMessage(true);
|
|
|
+ },
|
|
|
+ initialAudioPlayer () {
|
|
|
+ this.audioPlayer.innerAudioContext = uni.createInnerAudioContext();
|
|
|
+ this.audioPlayer.innerAudioContext.onEnded(() => {
|
|
|
+ this.audioPlayer.playingMessage = null;
|
|
|
+ });
|
|
|
+ this.audioPlayer.innerAudioContext.onStop(() => {
|
|
|
+ this.audioPlayer.playingMessage = null;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ initRecorderListeners() {
|
|
|
+ recorderManager.onRecordComplete((file, duration) => {
|
|
|
+ if (duration < 1000) {
|
|
|
+ uni.showToast({
|
|
|
+ icon: 'none',
|
|
|
+ title: '录音时间太短',
|
|
|
+ duration: 500
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ GoEasy.im.createAudioMessage({
|
|
|
+ to: this.to,
|
|
|
+ file: file,
|
|
|
+ notification: {
|
|
|
+ title: this.currentUser.supplierName + '发来一段语音',
|
|
|
+ body: '[语音消息]', // 字段最长 50 字符
|
|
|
+ sound: 'message',
|
|
|
+ badge: '+1'
|
|
|
+ },
|
|
|
+ onProgress: function (progress) {
|
|
|
+ console.log(progress)
|
|
|
+ },
|
|
|
+ onSuccess: (message) => {
|
|
|
+ this.sendMessage(message);
|
|
|
+ },
|
|
|
+ onFailed: (e) => {
|
|
|
+ console.log('error :', e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 核心就是设置高度,产生明确占位
|
|
|
+ *
|
|
|
+ * 小 (宽度和高度都小于预设尺寸)
|
|
|
+ * 设高=原始高度
|
|
|
+ * 宽 (宽度>高度)
|
|
|
+ * 高度= 根据宽度等比缩放
|
|
|
+ * 窄 (宽度<高度)或方(宽度=高度)
|
|
|
+ * 设高=MAX height
|
|
|
+ *
|
|
|
+ * @param width,height
|
|
|
+ * @returns number
|
|
|
+ */
|
|
|
+ getImageHeight(width, height) {
|
|
|
+ if (width < IMAGE_MAX_WIDTH && height < IMAGE_MAX_HEIGHT) {
|
|
|
+ return height * 2;
|
|
|
+ } else if (width > height) {
|
|
|
+ return (IMAGE_MAX_WIDTH / width * height) * 2;
|
|
|
+ } else if (width === height || width < height) {
|
|
|
+ return IMAGE_MAX_HEIGHT * 2;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ sendMessage(message) {
|
|
|
+ this.history.messages.push(message);
|
|
|
+ this.scrollToBottom();
|
|
|
+ GoEasy.im.sendMessage({
|
|
|
+ message: message,
|
|
|
+ onSuccess: function () {
|
|
|
+ console.log('发送成功.', message);
|
|
|
+ },
|
|
|
+ onFailed: function (error) {
|
|
|
+ if (error.code === 507) {
|
|
|
+ console.log('发送语音/图片/视频/文件失败,没有配置OSS存储,详情参考:https://docs.goeasy.io/2.x/im/message/media/alioss');
|
|
|
+ } else {
|
|
|
+ console.log('发送失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ sendTextMessage() {
|
|
|
+ if (this.text.trim() !== '') {
|
|
|
+ let body = this.text;
|
|
|
+ if (this.text.length >= 50) {
|
|
|
+ body = this.text.substring(0, 30) + '...';
|
|
|
+ }
|
|
|
+ GoEasy.im.createTextMessage({
|
|
|
+ text: this.text,
|
|
|
+ to: this.to,
|
|
|
+ notification: {
|
|
|
+ title: this.currentUser.supplierName + '发来一段文字',
|
|
|
+ body: body,
|
|
|
+ sound: 'message',
|
|
|
+ badge: '+1'
|
|
|
+ },
|
|
|
+ onSuccess: (message) => {
|
|
|
+ this.sendMessage(message);
|
|
|
+ },
|
|
|
+ onFailed: (e) => {
|
|
|
+ console.log('error :', e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ this.text = '';
|
|
|
+ },
|
|
|
+ sendVideoMessage() {
|
|
|
+ uni.chooseVideo({
|
|
|
+ success: (res) => {
|
|
|
+ GoEasy.im.createVideoMessage({
|
|
|
+ to: this.to,
|
|
|
+ file: res,
|
|
|
+ notification: {
|
|
|
+ title: this.currentUser.supplierName + '发来一个视频',
|
|
|
+ body: '[视频消息]', // 字段最长 50 字符
|
|
|
+ sound: 'message',
|
|
|
+ badge: '+1'
|
|
|
+ },
|
|
|
+ onProgress: function (progress) {
|
|
|
+ console.log(progress)
|
|
|
+ },
|
|
|
+ onSuccess: (message) => {
|
|
|
+ this.otherTypesMessagePanelVisible = false;
|
|
|
+ this.sendMessage(message);
|
|
|
+ },
|
|
|
+ onFailed: (e) => {
|
|
|
+ console.log('error :', e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ sendImageMessage() {
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 9,
|
|
|
+ success: (res) => {
|
|
|
+ res.tempFiles.forEach(file => {
|
|
|
+ GoEasy.im.createImageMessage({
|
|
|
+ to: this.to,
|
|
|
+ file: file,
|
|
|
+ notification: {
|
|
|
+ title: this.currentUser.supplierName + '发来一张图片',
|
|
|
+ body: '[图片消息]', // 字段最长 50 字符
|
|
|
+ sound: 'message',
|
|
|
+ badge: '+1'
|
|
|
+ },
|
|
|
+ onProgress: function (progress) {
|
|
|
+ console.log(progress)
|
|
|
+ },
|
|
|
+ onSuccess: (message) => {
|
|
|
+ this.otherTypesMessagePanelVisible = false;
|
|
|
+ this.sendMessage(message);
|
|
|
+ },
|
|
|
+ onFailed: (e) => {
|
|
|
+ console.log('error :', e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ sendOrderMessage(order) {
|
|
|
+ //GoEasyIM自定义消息,实现订单发送
|
|
|
+ GoEasy.im.createCustomMessage({
|
|
|
+ type: 'order',
|
|
|
+ payload: order,
|
|
|
+ to: this.to,
|
|
|
+ notification: {
|
|
|
+ title: this.currentUser.supplierName + '发来一个订单',
|
|
|
+ body: '[订单消息]',
|
|
|
+ sound: 'message',
|
|
|
+ badge: '+1'
|
|
|
+ },
|
|
|
+ onSuccess: (message) => {
|
|
|
+ this.otherTypesMessagePanelVisible = false;
|
|
|
+ this.sendMessage(message);
|
|
|
+ },
|
|
|
+ onFailed: (e) => {
|
|
|
+ console.log('error :', e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.orderList.visible = false;
|
|
|
+ },
|
|
|
+ showActionPopup(message) {
|
|
|
+ const MAX_RECALLABLE_TIME = 3 * 60 * 1000; //3分钟以内的消息才可以撤回
|
|
|
+ this.messageSelector.messages = [message];
|
|
|
+ if ((Date.now() - message.timestamp) < MAX_RECALLABLE_TIME && message.senderId === this.currentUser.userId && message.status === 'success') {
|
|
|
+ this.actionPopup.recallable = true;
|
|
|
+ } else {
|
|
|
+ this.actionPopup.recallable = false;
|
|
|
+ }
|
|
|
+ this.actionPopup.visible = true;
|
|
|
+ },
|
|
|
+ hideActionPopup () {
|
|
|
+ this.actionPopup.visible = false;
|
|
|
+ this.actionPopup.message = null;
|
|
|
+ },
|
|
|
+ deleteSingleMessage() {
|
|
|
+ uni.showModal({
|
|
|
+ content: '确认删除?',
|
|
|
+ success: (res) => {
|
|
|
+ this.actionPopup.visible = false;
|
|
|
+ if (res.confirm) {
|
|
|
+ this.deleteMessage();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ })
|
|
|
+ },
|
|
|
+ deleteMultipleMessages() {
|
|
|
+ if (this.messageSelector.messages.length > 0) {
|
|
|
+ uni.showModal({
|
|
|
+ content: '确认删除?',
|
|
|
+ success: (res) => {
|
|
|
+ this.messageSelector.visible = false;
|
|
|
+ if (res.confirm) {
|
|
|
+ this.deleteMessage();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deleteMessage() {
|
|
|
+ GoEasy.im.deleteMessage({
|
|
|
+ messages: this.messageSelector.messages,
|
|
|
+ onSuccess: (result) => {
|
|
|
+ this.messageSelector.messages.forEach(message => {
|
|
|
+ let index = this.history.messages.indexOf(message);
|
|
|
+ if (index > -1) {
|
|
|
+ this.history.messages.splice(index, 1);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.messageSelector.messages = [];
|
|
|
+ },
|
|
|
+ onFailed: (error) => {
|
|
|
+ console.log('error:', error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ recallMessage() {
|
|
|
+ this.actionPopup.visible = false;
|
|
|
+ GoEasy.im.recallMessage({
|
|
|
+ messages: this.messageSelector.messages,
|
|
|
+ onSuccess: () => {
|
|
|
+ console.log('撤回成功');
|
|
|
+ },
|
|
|
+ onFailed: (error) => {
|
|
|
+ console.log('撤回失败,error:', error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ editRecalledMessage(text) {
|
|
|
+ if (this.audio.visible) {
|
|
|
+ this.audio.visible = false;
|
|
|
+ }
|
|
|
+ this.text = text;
|
|
|
+ },
|
|
|
+ showCheckBox() {
|
|
|
+ this.messageSelector.messages = [];
|
|
|
+ this.messageSelector.visible = true;
|
|
|
+ this.actionPopup.visible = false;
|
|
|
+ },
|
|
|
+ selectMessages(e) {
|
|
|
+ const selectedMessageIds = e.detail.value;
|
|
|
+ let selectedMessages = [];
|
|
|
+ this.history.messages.forEach(message => {
|
|
|
+ if (selectedMessageIds.includes(message.messageId)) {
|
|
|
+ selectedMessages.push(message);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.messageSelector.messages = selectedMessages;
|
|
|
+ },
|
|
|
+ loadHistoryMessage(scrollToBottom) {//历史消息
|
|
|
+ this.history.loading = true;
|
|
|
+ let lastMessageTimeStamp = null;
|
|
|
+ let lastMessage = this.history.messages[0];
|
|
|
+ if (lastMessage) {
|
|
|
+ lastMessageTimeStamp = lastMessage.timestamp;
|
|
|
+ }
|
|
|
+ GoEasy.im.history({
|
|
|
+ id: this.friend.id,
|
|
|
+ type: GoEasy.IM_SCENE.PRIVATE,
|
|
|
+ lastTimestamp: lastMessageTimeStamp,
|
|
|
+ limit: 10,
|
|
|
+ onSuccess: (result) => {
|
|
|
+ uni.stopPullDownRefresh();
|
|
|
+ this.history.loading = false;
|
|
|
+ let messages = result.content;
|
|
|
+ if (messages.length === 0) {
|
|
|
+ this.history.allLoaded = true;
|
|
|
+ } else {
|
|
|
+ if (lastMessageTimeStamp) {
|
|
|
+ this.history.messages = messages.concat(this.history.messages);
|
|
|
+ } else {
|
|
|
+ this.history.messages = messages;
|
|
|
+ }
|
|
|
+ if (messages.length < 10) {
|
|
|
+ this.history.allLoaded = true;
|
|
|
+ }
|
|
|
+ if (scrollToBottom) {
|
|
|
+ this.scrollToBottom();
|
|
|
+ //收到的消息设置为已读
|
|
|
+ this.markPrivateMessageAsRead();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onFailed: (error) => {
|
|
|
+ //获取失败
|
|
|
+ console.log('获取历史消息失败:', error);
|
|
|
+ uni.stopPullDownRefresh();
|
|
|
+ this.history.loading = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ //语音录制按钮和键盘输入的切换
|
|
|
+ switchAudioKeyboard() {
|
|
|
+ if (!this.audio.visible) {
|
|
|
+ recorderManager.authorize().then(() => {
|
|
|
+ console.log('录音权限获取成功');
|
|
|
+ this.audio.visible = true;
|
|
|
+ }).catch((err) => {
|
|
|
+ console.log('err:', err)
|
|
|
+ uni.showModal({
|
|
|
+ title: '获取录音权限失败',
|
|
|
+ content: '请先打开麦克风权限'
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.audio.visible = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onRecordStart() {
|
|
|
+ recorderManager.start();
|
|
|
+ },
|
|
|
+ onRecordEnd() {
|
|
|
+ recorderManager.stop();
|
|
|
+ },
|
|
|
+ showImageFullScreen(e) {
|
|
|
+ let imagesUrl = [e.currentTarget.dataset.url];
|
|
|
+ uni.previewImage({
|
|
|
+ urls: imagesUrl
|
|
|
+ });
|
|
|
+ },
|
|
|
+ playVideo(e) {
|
|
|
+ //this.videoPlayer.visible = true;
|
|
|
+ this.videoPlayer.url = e.currentTarget.dataset.url;
|
|
|
+ this.videoShow=true
|
|
|
+ /* this.$nextTick(() => {
|
|
|
+ this.videoPlayer.context.requestFullScreen({
|
|
|
+ direction: 0
|
|
|
+ });
|
|
|
+ this.videoPlayer.context.play();
|
|
|
+ }); */
|
|
|
+ },
|
|
|
+ playAudio (audioMessage) {
|
|
|
+ let playingMessage = this.audioPlayer.playingMessage;
|
|
|
+
|
|
|
+ if (playingMessage) {
|
|
|
+ this.audioPlayer.innerAudioContext.stop();
|
|
|
+ // 如果点击的消息正在播放,就认为是停止播放操作
|
|
|
+ if (playingMessage === audioMessage) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.audioPlayer.playingMessage = audioMessage;
|
|
|
+ this.audioPlayer.innerAudioContext.src = audioMessage.payload.url;
|
|
|
+ this.audioPlayer.innerAudioContext.play();
|
|
|
+ },
|
|
|
+ onVideoFullScreenChange(e) {
|
|
|
+ //当退出全屏播放时,隐藏播放器
|
|
|
+ if (this.videoPlayer.visible && !e.detail.fullScreen) {
|
|
|
+ this.videoPlayer.visible = false;
|
|
|
+ this.videoPlayer.context.stop();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ messageInputFocusin() {
|
|
|
+ this.otherTypesMessagePanelVisible = false;
|
|
|
+ this.emoji.visible = false;
|
|
|
+ },
|
|
|
+ switchEmojiKeyboard() {
|
|
|
+ this.emoji.visible = !this.emoji.visible;
|
|
|
+ this.otherTypesMessagePanelVisible = false;
|
|
|
+ },
|
|
|
+ showOtherTypesMessagePanel() {
|
|
|
+ this.otherTypesMessagePanelVisible = !this.otherTypesMessagePanelVisible;
|
|
|
+ this.emoji.visible = false;
|
|
|
+ },
|
|
|
+ chooseEmoji(emojiKey) {
|
|
|
+ this.text += emojiKey;
|
|
|
+ },
|
|
|
+ showOrderMessageList() {
|
|
|
+ this.orderList.orders = restApi.getOrderList();
|
|
|
+ this.orderList.visible = true;
|
|
|
+ },
|
|
|
+ hideOrderMessageList() {
|
|
|
+ this.orderList.visible = false;
|
|
|
+ },
|
|
|
+ privateCall() {
|
|
|
+ uni.showActionSheet({
|
|
|
+ itemList: ['视频通话', '音频通话'],
|
|
|
+ success: (res) => {
|
|
|
+ const mediaType = res.tapIndex === 0 ? 1 : 0;
|
|
|
+ const notificationBody = res.tapIndex === 0 ? '邀请你视频通话' : '邀请你语音通话';
|
|
|
+ GRTC.call({
|
|
|
+ calleeId: this.friend.id,
|
|
|
+ mediaType: mediaType,
|
|
|
+ notification: {
|
|
|
+ title: this.currentUser.name,
|
|
|
+ body: notificationBody,
|
|
|
+ sound: 'ring',
|
|
|
+ badge: '+1'
|
|
|
+ },
|
|
|
+ }).then(() => {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: `./rtc/private/dial`,
|
|
|
+ })
|
|
|
+ }).catch((error)=>{
|
|
|
+ console.log("呼叫失败:", error);
|
|
|
+ uni.showToast({
|
|
|
+ icon: "error",
|
|
|
+ title: "呼叫失败:" + error,
|
|
|
+ duration: 2000
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ fail: (res) => {
|
|
|
+ console.log(res.errMsg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ scrollToBottom() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ uni.pageScrollTo({
|
|
|
+ scrollTop: 2000000,
|
|
|
+ duration: 0
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ markPrivateMessageAsRead() {
|
|
|
+ GoEasy.im.markMessageAsRead({
|
|
|
+ id: this.to.id,
|
|
|
+ type: this.to.type,
|
|
|
+ onSuccess: function () {
|
|
|
+ console.log('标记私聊已读成功');
|
|
|
+ },
|
|
|
+ onFailed: function (error) {
|
|
|
+ console.log("标记私聊已读失败", error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ @import url('../static/style/chatInterface.css');
|
|
|
+ .videoBox{
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ background: rgba(0,0,0,0.5);
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ #videoPlayer{
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+</style>
|