goeasy对接(uniapp-vue2)

官方文档 初始化与建立连接 | GoEasy文档

官方dome下载地址 IM即时通讯聊天(Uniapp 小程序 Vue React Taro H5): IM即时通讯聊天, 为Web前端开发者打造,天生支持H5、Uniapp和各种小程序的IM即时通讯, 快速实现私聊、群聊、在线客服!

1.引入sdk,可在demo里直接复制。引入静态资源(图片及css文件)

 --- lib文件夹在demo有,可以直接复制,图片资源和style目录里的文件demo也有,路径保持和图中一致。

2.后台创建goeasy应用后,在main.js中初始化goeasy

import GoEasy from '@/uni_modules/GOEASY-IM/js_sdk/goeasy-2.13.2.esm.min.js'
import GRTC from './lib/goeasy-rtc-0.2.1.esm.min.js'

GoEasy.init({
    host: "hangzhou.goeasy.io", //应用所在的区域地址: 【hangzhou.goeasy.io |singapore.goeasy.io】
    appkey: "xxxxxx", // common key
    modules: ['im'],
    // true表示支持通知栏提醒,false则表示不需要通知栏提醒
    allowNotification: true //仅有效于app,小程序和H5将会被自动忽略
});
GRTC.init(GoEasy);

//定义全局变量

uni.$GoEasy = GoEasy;

uni.$GRTC = GRTC;

3.创建im文件夹,新建index.vue(会话列表) , chat.vue(一对一单聊),文件内容在最下方。

4.goeasy免费日活十五个用户,为了节省日活只有在点击聊天按钮时连接goeasy,下面是私聊方法

             const GoEasy = uni.$GoEasy;

            // 聊天
            async goIM(item) {
                if (!this.$u.func.checkLogin()) return
                if (this.userInfo.id == item.user_id) return this.$u.toast("您不能与自己聊天")
                uni.showLoading();
                GoEasy.connect({
                    id: this.userInfo.id + '', //im必填,最大长度60字符
                    data: {
                        "avatar": this.userInfo.avatar,
                        "name": this.userInfo.nickname 
                    },
                    onSuccess: () => {
                        console.log("链接成功");
                        this.goImChat(item)
                    },
                    onFailed: (error) => {
                        console.log('Failed to connect GoEasy, code:' + error.code + ',error:' + error
                            .content);
                        if (error.code == 408) {
                            console.log("已连接请务重复链接");
                            this.goImChat(item)
                            return
                        }
                        this.$u.toast(error.content)
                    },
                    onProgress: (attempts) => {
                        console.log('GoEasy is connecting', attempts);
                    }
                });

            },
            goImChat(item) {
                uni.hideLoading()

                 //私聊对方数据
                let toData = {
                    avatar: item.avatar,
                    id: item.user_id + '',
                    phone: item.phone,
                    name: item.nickname,
                }

                 //当前用户数据
                let userInfo = {
                    avatar: this.userInfo.avatar,
                    email: '暂无',
                    id: this.userInfo.id + '',
                    phone: this.userInfo.mobile,
                    name: this.userInfo.nickname 
                }
                uni.$currentUser = userInfo
                uni.setStorageSync('toData', toData)
                uni.navigateTo({
                    url: `/pages/im/chat?to=${item.user_id + ''}`
                })
            },

5.如需发送图片和视频,需要配置云存储,下面是官方配置文档链接

阿里云 配置阿里云对象存储OSS - IM即时通讯 | GoEasy文档

腾讯云 https://docs.goeasy.io/2.x/im/message/media/tencentcos

index.vue文件

<template>
    <view class="container">

        <scroll-view class="conversations" scroll-y="true">
            <view v-if="conversations.length > 0">
                <view class="scroll-item" v-for="(conversation, key) in conversations" :key="key">
                    <view class="item-head" style="position: relative;">
                        <image :src="conversation.data.avatar" class="head-icon"
                            style="width: 80rpx;height: 80rpx;border-radius: 50%;" mode="aspectFill"></image>
                        <view class="item-head_unread" v-if="conversation.unread">
                            {{ conversation.unread >=99 ? 99 : conversation.unread}}
                        </view>
                    </view>
                    <view class="scroll-item_info" @click="chat(conversation)">
                        <view class="item-info-top" style="font-size: 28rpx;">
                            <text class="item-info-top_name"
                                style="font-weight:bolder;">{{ conversation.data.name }}</text>
                            <view class="item-info-top_time" style="font-size: 24rpx;">
                                {{ formatDate(conversation.lastMessage.timestamp) }}
                            </view>
                        </view>
                        <view class="item-info-bottom" style="margin-top: 10rpx;">
                            <view class="item-info-bottom-item">
                                <view class="item-info-top_content" v-if="!conversation.lastMessage.recalled">
                                    <text class="unread-text" style="font-size: 24rpx;">
                                        {{ conversation.lastMessage.read === false && conversation.lastMessage.senderId === currentUser.id ? '[未读]' : '' }}
                                    </text>

                                    <text v-if="conversation.lastMessage.senderId === currentUser.id"
                                        style="font-size: 24rpx;">我: </text>
                                    <text style="font-size: 24rpx;"
                                        v-else>{{ conversation.type === 'group' ? conversation.lastMessage.senderData.name : conversation.data.name }}:
                                    </text>
                                    <text style="font-size: 24rpx;"
                                        v-if="conversation.lastMessage.type === 'text'">{{ conversation.lastMessage.payload.text }}</text>
                                    <text style="font-size: 24rpx;"
                                        v-else-if="conversation.lastMessage.type === 'video'">[视频消息]</text>
                                    <text style="font-size: 24rpx;"
                                        v-else-if="conversation.lastMessage.type === 'audio'">[语音消息]</text>
                                    <text style="font-size: 24rpx;"
                                        v-else-if="conversation.lastMessage.type === 'image'">[图片消息]</text>
                                    <text style="font-size: 24rpx;"
                                        v-else-if="conversation.lastMessage.type === 'file'">[文件消息]</text>
                                    <text style="font-size: 24rpx;"
                                        v-else-if="conversation.lastMessage.type === 'order'">[自定义消息:订单]</text>
                                    <text style="font-size: 24rpx;" v-else>[[未识别内容]]</text>
                                </view>
                                <view class="item-info-top_content" v-else>
                                    <text style="font-size: 24rpx;">
                                        {{conversation.lastMessage.recaller.id === currentUser.id ? '你' : conversation.lastMessage.recaller.data.name}}撤回了一条消息
                                    </text>
                                </view>
                                <view class="item-info-bottom_action" @click.stop="showAction(conversation)"></view>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="no-conversation" v-else>当前没有会话</view>
            <view class="action-container" v-if="actionPopup.visible">
                <view class="layer" @click="actionPopup.visible = false"></view>
                <view class="action-box">
                    <view class="action-item" @click="topConversation">
                        {{ actionPopup.conversation.top ? '取消置顶' : '置顶聊天' }}
                    </view>
                    <view class="action-item" @click="deleteConversation">删除聊天</view>
                </view>
            </view>
        </scroll-view>
    </view>

</template>

<script>
    import {
        formatDate
    } from '@/lib/utils';
    import restApi from '@/lib/restapi';

    const GoEasy = uni.$GoEasy;
    const GRTC = uni.$GRTC;
    export default {
        name: 'conversation',
        data() {
            return {
                conversations: [],

                actionPopup: {
                    conversation: null,
                    visible: false
                },
                currentUser: null,

            }
        },
        onShow() {
            // this.currentUser = this.userInfo
            let userInfo = {
                avatar: this.userInfo.avatar,
                id: this.userInfo.id + '',
                phone: this.userInfo.mobile,
                name: this.userInfo.nickname,

            }
            // if (this.type == 1) userInfo.name = this.userInfo.nickname
            uni.$currentUser = userInfo
            this.currentUser = uni.$currentUser
            if (GoEasy.getConnectionStatus() === 'disconnected') {
                this.connectGoEasy(); //连接goeasy
                this.subscribeGroup(); //建立连接后,就应该订阅群聊消息,避免漏掉
            }
            this.loadConversations(); //加载会话列表
            this.initGoEasyListeners();
        },
        onLoad(op) {
            this.type = op.type
        },
        beforeDestroy() {
            GoEasy.im.off(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.renderConversations);
        },
        methods: {
            // 前往系统消息页面
            goInfo() {
                uni.navigateTo({
                    url: this.type == 1 ? `/pages/order/system-info` : `/pages/teacher-pages/order/system-info`
                })
            },
            formatDate,
            connectGoEasy() {
                uni.showLoading();
                GoEasy.connect({
                    id: this.currentUser.id, //im必填,最大长度60字符
                    data: {
                        "avatar": this.currentUser.avatar,
                        "name": this.currentUser.name,
                    },
                    onSuccess: () => {
                        console.log("链接成功");
                        console.log('GoEasy connect successfully.--')
                    },
                    onFailed: (error) => {
                        console.log('Failed to connect GoEasy, code:' + error.code + ',error:' + error
                            .content);
                    },
                    onProgress: (attempts) => {
                        console.log('GoEasy is connecting', attempts);
                    }
                });
            },
            initGoEasyListeners() {
                GoEasy.im.on(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.renderConversations); //监听会话列表变化
                GoEasy.im.off(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.setUnreadAmount); // 移除之前的设置角标回调,防止重复回调
                GoEasy.im.on(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, this.setUnreadAmount); // 设置角标
                // #ifdef APP-PLUS
                GRTC.off(GRTC.EVENT.RING, this.onRing); //移除之前的监听来电事件,防止重复回调
                GRTC.on(GRTC.EVENT.RING, this.onRing); //监听来电事件
                // #endif
            },
            onRing() {
                const currentCall = GRTC.currentCall();
                if (currentCall.groupId) {
                    uni.navigateTo({
                        url: `./rtc/group/ring`,
                    })
                } else {
                    uni.navigateTo({
                        url: `./rtc/private/ring`,
                    })
                }
            },

            // 加载最新的会话列表
            loadConversations() {
                GoEasy.im.latestConversations({
                    onSuccess: (result) => {
                        uni.hideLoading();
                        let content = result.content;
                        this.renderConversations(content);
                        this.setUnreadAmount(content);
                    },
                    onFailed: (error) => {
                        uni.hideLoading();
                        console.log('获取最新会话列表失败, error:', error);
                    }
                });
            },
            renderConversations(content) {
                this.conversations = content.conversations;
            },
            setUnreadAmount(content) {
                const unreadTotal = content.unreadTotal;
                if (unreadTotal > 0) {
                    uni.setTabBarBadge({
                        index: 0,
                        text: unreadTotal.toString()
                    });
                } else {
                    uni.removeTabBarBadge({
                        index: 0
                    });
                }
                // #ifdef APP-PLUS
                GoEasy.setBadge({
                    badge: unreadTotal,
                    onSuccess: function() {
                        console.log("setBadge successfully.")
                    },
                    onFailed: function(error) {
                        console.log("Failed to setBadge,error:" + error);
                    }
                });
                // #endif
            },
            subscribeGroup() {
                let groups = restApi.findGroups(this.currentUser);
                let groupIds = groups.map(item => item.id);
                GoEasy.im.subscribeGroup({
                    groupIds: groupIds,
                    onSuccess: function() {
                        console.log('订阅群消息成功');
                    },
                    onFailed: function(error) {
                        console.log('订阅群消息失败:', error);
                    }
                });
            },
            topConversation() { //会话置顶
                this.actionPopup.visible = false;
                let conversation = this.actionPopup.conversation;
                let description = conversation.top ? '取消置顶' : '置顶';
                GoEasy.im.topConversation({
                    conversation: conversation,
                    top: !conversation.top,
                    onSuccess: function() {
                        uni.showToast({
                            title: description + '成功',
                            icon: 'none'
                        });
                    },
                    onFailed: function(error) {
                        console.log(description, '失败:', error);
                    }
                });
            },
            deleteConversation() {
                uni.showModal({
                    content: '确认删除这条会话吗?',
                    success: (res) => {
                        if (res.confirm) {
                            let conversation = this.actionPopup.conversation;
                            this.actionPopup.visible = false;
                            GoEasy.im.removeConversation({
                                conversation: conversation,
                                onSuccess: function() {
                                    console.log('删除会话成功');
                                },
                                onFailed: function(error) {
                                    console.log(error);
                                },
                            });
                        } else {
                            this.actionPopup.visible = false;
                        }
                    },
                })
            },
            chat(conversation) {
                let path = conversation.type === GoEasy.IM_SCENE.PRIVATE ?
                    `/pages/im/chat?to=${conversation.userId}` :
                    '/pages/im/chat?to=' + conversation.groupId;
                console.log(path);
                let toData = {
                    avatar: conversation.data.avatar,
                    id: conversation.userId + '',
                    name: conversation.data.name,
                }
                uni.setStorageSync('toData', toData)
                uni.navigateTo({
                    url: path
                });
            },
            showAction(conversation) {
                this.actionPopup.conversation = conversation;
                this.actionPopup.visible = true;
            }
        }
    }
</script>

<style>
    page {
        height: 100%;
    }

    .container {
        height: 100%;
        background-color: white;
    }

    .conversations {
        width: 750rpx;
        overflow-x: hidden;
        display: flex;
        flex-direction: column;
        height: 100%;
        background-color: white;
    }

    .conversations .scroll-item {
        height: 152rpx;
        display: flex;
        align-items: center;
        padding-left: 32rpx;
    }

    .conversations .scroll-item .head-icon {
        width: 100rpx;
        height: 100rpx;
        margin-right: 28rpx;
    }

    .conversations .scroll-item_info {
        height: 151rpx;
        width: 590rpx;
        padding-right: 32rpx;
        border-bottom: 1px solid #EFEFEF;
    }

    .conversations .scroll-item_info .item-info-top {
        padding-top: 20rpx;
        height: 60rpx;
        line-height: 60rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;

    }

    .conversations .item-info-top_name {
        font-size: 34rpx;
        color: #262628;
    }

    .conversations .item-info-top_time {
        font-size: 34rpx;
        color: rgba(179, 179, 179, 0.8);
    }

    .conversations .item-info-bottom {
        height: 40rpx;
        line-height: 40rpx;
        overflow: hidden;
    }

    .conversations .item-info-bottom-item {
        display: flex;
        justify-content: space-between;
    }

    .item-info-bottom .item-info-top_content {
        font-size: 34rpx;
        color: #b3b3b3;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;

    }

    .item-info-bottom .item-info-bottom_action {
        width: 50rpx;
        height: 50rpx;
        font-size: 34rpx;
        background: url("@/static/goeasyImg/action.png") no-repeat center;
        background-size: 28rpx 30rpx;
    }

    .no-conversation {
        width: 100%;
        text-align: center;
        height: 80rpx;
        line-height: 80rpx;
        font-size: 34rpx;
        color: #9D9D9D;
    }

    .item-head {
        position: relative;
    }

    .item-head .item-head_unread {
        /* padding: 6rpx; */
        background-color: #EE593C;
        color: #FFFFFF;
        font-size: 20rpx;
        line-height: 28rpx;
        border-radius: 50%;
        /* min-width: 24rpx;
        min-height: 24rpx; */
        width: 30rpx;
        height: 30rpx;
        line-height: 30rpx;
        text-align: center;
        position: absolute;
        top: 0;
        right: 15rpx;
    }

    .action-container {
        width: 100%;
        height: 100%;
        position: fixed;
        top: 0;
        left: 0;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .action-container .layer {
        position: absolute;
        top: 0;
        left: 0;
        background: rgba(51, 51, 51, 0.5);
        width: 100%;
        height: 100%;
        z-index: 99;
    }

    .action-box {
        width: 400rpx;
        height: 240rpx;
        background: #ffffff;
        position: relative;
        z-index: 100;
        border-radius: 20rpx;
        overflow: hidden;
    }

    .action-item {
        text-align: center;
        line-height: 120rpx;
        font-size: 34rpx;
        color: #262628;
        border-bottom: 1px solid #EFEFEF;
    }

    .unread-text {
        color: #d02129;
    }
</style>

chat.vue文件

<template>
    <view class="chatInterface" @contextmenu.prevent="">
        <view class="scroll-view">
            <image v-if="history.loading" class="history-loaded" src="/static/goeasyImg/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.id" 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.id}">
                            <view class="avatar">
                                <image :src="message.senderId === currentUser.id? currentUser.avatar : friend.avatar"
                                    style="width: 80rpx;height: 80rpx;border-radius: 50%;" mode="aspectFill">
                                </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/goeasyImg/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>
                                <view v-if="message.senderId === currentUser.id"
                                    :class="message.read ?'message-read':'message-unread'">
                                    <view v-if="message.status === 'success'" style="font-size: 22rpx;">
                                        {{ 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/goeasyImg/jianpan.png"></image>
                    <image class="more" v-else src="/static/goeasyImg/audio.png"></image>
                </view>
                <view v-if="audio.visible" class="record-input" @touchend.stop="onRecordEnd"
                    @touchstart.stop="onRecordStart">
                    {{ recorderManager.recording ? '松开发送' : '按住录音' }}
                </view> -->

                <input 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/goeasyImg/jianpan.png"></image>
                    <image class="more" v-else src="/static/goeasyImg/emoji.png"></image>
                </view>
                <view>
                    <image @click="showOtherTypesMessagePanel()" class="more" src="/static/goeasyImg/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/goeasyImg/picture.png">
                    </image>
                    <view class="operation-title">图片</view>
                </view>
                <view class="more-icon">
                    <image @click="sendVideoMessage()" class="operation-icon" src="/static/goeasyImg/video.png"></image>
                    <view class="operation-title">视频</view>
                </view>
                <!--                 <view class="more-icon">
                    <image @click="showOrderMessageList()" class="operation-icon" src="/static/goeasyImg/order.png">
                    </image>
                    <view class="operation-title">订单</view>
                </view>
                <view class="more-icon">
                    <image @click="privateCall()" class="operation-icon" src="/static/goeasyImg/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/goeasyImg/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>
</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: []
                },
                order_type: 1, //1是用户自己购买 2是老师给用户购买,
                teacher_id: '',
                for_user_id: '', //老师给用户买时传对方用户的id
                need_id: '', //需求id,老师给学生下单传
            }
        },
        onLoad(options) {
            this.order_type = options.order_type
            this.teacher_id = options.teacher_id
            this.for_user_id = options.for_user_id
            this.need_id = options.need_id

            //聊天对象
            let id = options.to;
            // this.friend = restApi.findUserById(id);
            this.friend = uni.getStorageSync('toData')
            this.currentUser = uni.$currentUser;
            this.to = {
                id: this.friend.id,
                type: GoEasy.IM_SCENE.PRIVATE,
                data: {
                    name: this.friend.name,
                    avatar: this.friend.avatar,
                    teacher_id: this.friend.teacher_id || 0
                }
            };

            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.teacher_id ? this.friend.name + '老师' : this.friend.name
            });
        },
        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);
        },
        methods: {
            goPay() {
                if (!this.$u.func.checkLogin()) return
                let url = `/pages/order/pay-course?teacher_id=${this.teacher_id}&order_type=${this.order_type}`
                if (this.order_type == 2) url =
                    `/pages/order/pay-course?teacher_id=${this.teacher_id}&order_type=${this.order_type}&for_user_id=${this.for_user_id}&need_id=${this.need_id}`
                uni.navigateTo({
                    url
                })
            },
            //渲染文本消息,如果包含表情,替换为图片
            //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);
            },
            onMessageReceived(message) {
                let senderId = message.senderId;
                let receiverId = message.receiverId;
                let friendId = this.currentUser.id === 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.id === senderId ? receiverId : senderId;
                    if (friendId === this.friend.id) {
                        let index = this.history.messages.indexOf(message);
                        if (index > -1) {
                            this.history.messages.splice(index, 1);
                        }
                    }
                });
            },
            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.name + '发来一段语音',
                            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(message) {
                        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.name + '发来一段文字',
                            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.name + '发来一个视频',
                                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.name + '发来一张图片',
                                    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.name + '发来一个订单',
                        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.id &&
                    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.$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>
    @import url('@/static/style/chatInterface.css');
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值