前端对接腾讯云IM即时通讯

1,231 阅读10分钟

近期需求 : 在项目里面开发IM 即时通讯 之前对接过融云 GoEasy 极光 这次在微信小程序里面集成 本着" 同源策略 " 采用了腾讯云IM 即时通讯 虽然腾讯方面的文档对我来说并不是那么的友好 ☺

即时通信 IM官网地址: cloud.tencent.com/document/pr…

image.png

鉴于小程序体积的限制外加本公司对UI还原程度 "高度重视" , 并没有采用含UI集成方案 , 采用了无UI集成方案

半成品

image.png

image.png

image.png

步骤:

集成 SDK

  • 通过 npm 和 script 方式将 IM SDK 集成到您的 Web 项目中,推荐使用 npm 集成。
  • 通过 npm 方式将 IM SDK 集成到您的小程序或者 uni-app 项目中。
  • 通过集成上传插件 tim-upload-plugin,实现更快更安全的富文本消息资源上传。
  • 通过集成本地审核插件 tim-profanity-filter-plugin,在客户端本地检测由即时通信 SDK 发送的文本内容,支持对已配置的敏感词进行拦截或者替换处理,为您的产品体验和业务安全保驾护航。本地审核功能的开通和配置方法,详情请参见 控制台指南
// 从v2.11.2起,SDK 支持了 WebSocket,推荐接入;v2.10.2及以下版本,使用 HTTP
npm install tim-wx-sdk --save
// 发送图片、文件等消息需要腾讯云 即时通信 IM 上传插件
npm install tim-upload-plugin --save
// 拦截或替换敏感词需要本地审核插件
npm install tim-profanity-filter-plugin --save

初始化

初始化 SDK 需要操作以下步骤:

  1. 准备 SDKAppID。
  2. 调用 TIM.create 初始化 SDK。
  3. 添加 SDK 事件监听器。
// 从v2.11.2起,SDK 支持了 WebSocket,推荐接入;v2.10.2及以下版本,使用 HTTP
// v2.24.0起,SDK 支持使用本地审核插件
import TIM from 'tim-js-sdk';
import TIMUploadPlugin from 'tim-upload-plugin';
import TIMProfanityFilterPlugin from 'tim-profanity-filter-plugin';

let options = {
  SDKAppID: 0 // 接入时需要将0替换为您的即时通信 IM 应用的 SDKAppID
};
// 创建 SDK 实例,`TIM.create()`方法对于同一个 `SDKAppID` 只会返回同一份实例
let tim = TIM.create(options); // SDK 实例通常用 tim 表示

// 设置 SDK 日志输出级别,详细分级请参见 <a href="https://web.sdk.qcloud.com/im/doc/zh-cn/SDK.html#setLogLevel">setLogLevel 接口的说明</a>
tim.setLogLevel(0); // 普通级别,日志量较多,接入时建议使用
// tim.setLogLevel(1); // release 级别,SDK 输出关键信息,生产环境时建议使用

// 注册腾讯云即时通信 IM 上传插件
tim.registerPlugin({'tim-upload-plugin': TIMUploadPlugin});

// 注册腾讯云即时通信 IM 本地审核插件
tim.registerPlugin({'tim-profanity-filter-plugin': TIMProfanityFilterPlugin});

登录

let promise = tim.login({userID: 'your userID', userSig: 'your userSig'});
promise.then(function(imResponse) {
  console.log(imResponse.data); // 登录成功
  if (imResponse.data.repeatLogin === true) {
    // 标识帐号已登录,本次登录操作为重复登录。v2.5.1 起支持
    console.log(imResponse.data.errorInfo);
  }
}).catch(function(imError) {
  console.warn('login error:', imError); // 登录失败的相关信息
});

创建消息

image.png

接收消息 事件监听

let onMessageReceived = function(event) {
  // event.data - 存储 Message 对象的数组 - [Message]
  const messageList = event.data;
  messageList.forEach((message) => {
    if (message.type === TIM.TYPES.MSG_TEXT) {
      // 文本消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.TextPayload
    } else if (message.type === TIM.TYPES.MSG_IMAGE) {
      // 图片消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.ImagePayload
    } else if (message.type === TIM.TYPES.MSG_SOUND) {
      // 音频消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.AudioPayload
    } else if (message.type === TIM.TYPES.MSG_VIDEO) {
      // 视频消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.VideoPayload
    } else if (message.type === TIM.TYPES.MSG_FILE) {
      // 文件消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.FilePayload
    } else if (message.type === TIM.TYPES.MSG_CUSTOM) {
      // 自定义消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.CustomPayload
    } else if (message.type === TIM.TYPES.MSG_MERGER) {
      // 合并消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.MergerPayload
    } else if (message.type === TIM.TYPES.MSG_LOCATION) {
      // 地理位置消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.LocationPayload
    } else if (message.type === TIM.TYPES.MSG_GRP_TIP) {
      // 群提示消息 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.GroupTipPayload
    } else if (message.type === TIM.TYPES.MSG_GRP_SYS_NOTICE) {
      // 群系统通知 - https://web.sdk.qcloud.com/im/doc/zh-cn/Message.html#.GroupSystemNoticePayload
    }
  });
};
tim.on(TIM.EVENT.MESSAGE_RECEIVED, onMessageReceived);

历史消息

// 打开某个会话时,第一次拉取消息列表
let promise = tim.getMessageList({conversationID: 'C2Ctest'});
promise.then(function(imResponse) {
  const messageList = imResponse.data.messageList; // 消息列表。
  const nextReqMessageID = imResponse.data.nextReqMessageID; // 用于续拉,分页续拉时需传入该字段。
  const isCompleted = imResponse.data.isCompleted; // 表示是否已经拉完所有消息。
});
// 下拉查看更多消息
let promise = tim.getMessageList({conversationID: 'C2Ctest', nextReqMessageID});
promise.then(function(imResponse) {
  const messageList = imResponse.data.messageList; // 消息列表。
  const nextReqMessageID = imResponse.data.nextReqMessageID; // 用于续拉,分页续拉时需传入该字段。
  const isCompleted = imResponse.data.isCompleted; // 表示是否已经拉完所有消息。
});

获取会话列表

接入侧可通过调用 getConversationList 接口主动获取会话列表。

获取全量的会话列表

// 获取全量的会话列表
let promise = tim.getConversationList();
promise.then(function(imResponse) {
  const conversationList = imResponse.data.conversationList; // 全量的会话列表,用该列表覆盖原有的会话列表
  const isSyncCompleted = imResponse.data.isSyncCompleted; // 从云端同步会话列表是否完成
}).catch(function(imError) {
  console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息
});

获取指定的会话列表

// 获取指定的会话列表
let promise = tim.getConversationList([conversationID1, conversationID2]);
promise.then(function(imResponse) {
  const conversationList = imResponse.data.conversationList; // 缓存中已存在的指定的会话列表
}).catch(function(imError) {
  console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息
});

获取所有的群会话

// 获取所有的群会话
let promise = tim.getConversationList({ type: TIM.TYPES.CONV_GROUP });
promise.then(function(imResponse) {
  const conversationList = imResponse.data.conversationList; // 会话列表
});

获取所有的“标星”会话

// 获取所有的“标星”会话
let promise = tim.getConversationList({ markType: TIM.TYPES.CONV_MARK_TYPE_STAR });
promise.then(function(imResponse) {
  const conversationList = imResponse.data.conversationList; // 会话列表
});

获取指定会话分组下的所有会话

// 获取指定会话分组下的所有会话
let promise = tim.getConversationList({ groupName: 'Suppliers' });
promise.then(function(imResponse) {
  const conversationList = imResponse.data.conversationList; // 会话列表
});

监听会话列表更新事件

接入侧监听 TIM.EVENT.CONVERSATION_LIST_UPDATED 事件,获取会话列表更新的通知。

示例

let onConversationListUpdated = function(event) {
  console.log(event.data); // 包含 Conversation 实例的数组
};
tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, onConversationListUpdated);

置顶会话

功能描述

会话置顶,指的是把好友或者群会话固定在会话列表的最前面,方便用户查找。置顶状态会存储在服务器,切换终端设备后,置顶状态会同步到新设备上。
调用接口成功后会话列表重新排序,SDK 会派发事件 TIM.EVENT.CONVERSATION_LIST_UPDATED

// 置顶会话,v2.14.0起支持
let promise = tim.pinConversation({ conversationID: 'C2CExample', isPinned: true });
promise.then(function(imResponse) {
  // 置顶会话成功
  const { conversationID } = imResponse.data; // 被置顶的会话 ID
}).catch(function(imError) {
  console.warn('pinConversation error:', imError); // 置顶会话失败的相关信息
});
// 取消置顶会话,v2.14.0起支持
let promise = tim.pinConversation({ conversationID: 'C2CExample', isPinned: false });
promise.then(function(imResponse) {
  // 取消置顶会话成功
  const { conversationID } = imResponse.data; // 被取消置顶的会话 ID
}).catch(function(imError) {
  console.warn('pinConversation error:', imError); // 取消置顶会话失败的相关信息
});

删除会话

功能描述

在删除好友或退出群组后,如果不需要查看好友或群会话的历史消息,可以选择删除会话。会话删除默认关闭多端同步,可在 即时通信 IM 控制台 开启多端同步。

let promise = tim.deleteConversation('C2CExample');
promise.then(function(imResponse) {
  // 删除会话成功
  const { conversationID } = imResponse.data; // 被删除的会话 ID
}).catch(function(imError) {
  console.warn('deleteConversation error:', imError); // 删除会话失败的相关信息
});

具体建议还是打开官网进行开发 功能不难 本次记录只是方便后期相同需求开发快速迭代

chitchat.wxml

<!--pages/contact/contact.wxml-->


<custom-nav-bar backgroundColor="#000" color="#fff" isBottomBorder="{{false}}">
  <view class="nav-box">
    {{ navbarTitle }}
  </view>
</custom-nav-bar>

<view class="msg-list-box">
  <scroll-view scroll-y scroll-into-view='{{toView}}' scroll-with-animation bindscrolltoupper="bindscrolltoupperHandler" style='height: 80vh;' enable-passive bindtap="clickScrollView">
    <!-- <view class='scrollMsg'> -->
    <block wx:key="{{ index }}" wx:for='{{messageList}}'>
      <view wx:if="{{ item.showMessageTime }}" class="message-time">
        <text>{{ item.messageTime }}</text>
      </view>
      <!--(右) -->
      <view wx:if="{{ item.flow == 'out' }}" id='msg-{{index}}' class="right-msg-list-box">
        <view class="remoteAudioUrl" bindtap="innerAudioHandler" data-audio="{{ item.payload.remoteAudioUrl }}"  style="justify-content: flex-end;padding-right: 30rpx;width: {{ item.payload.second*20 }}rpx;max-width: 650rpx;min-width: 132rpx;" wx:if="{{ item.type == 'TIMSoundElem' }}">
          <text style="color: #fff;margin-right: 20rpx;">{{ item.payload.second }}''</text>
          <image src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-202302281147%E8%AF%AD%E9%9F%B3%20%E6%8B%B7%E8%B4%9D%402x.png" mode="widthFix" style="width: 22rpx;" />
        </view>
        <view class='rightMsg' wx:if="{{ item.type == 'TIMTextElem' }}">{{item.payload.text}}</view>
        <image style="width: 300rpx;border-radius: 10rpx;" bindtap="getLookBigImage" data-imgurl="{{item.payload.imageInfoArray[0].imageUrl}}" mode="widthFix" wx:if="{{ item.type == 'TIMImageElem' }}" src="{{ item.payload.imageInfoArray[0].imageUrl }}"></image>
        <!-- <view wx:if="{{ item.type == 'TIMTextElem' || item.type == 'TIMSoundElem' }}" style='width: 4vw; height: 11vw; margin-right: 16rpx; display: flex; align-items: center; z-index: 9;'>
          <image style='width: 4vw;' src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im---%E4%B8%89%E8%A7%92%E5%BD%A2%201%20%E6%8B%B7%E8%B4%9D%207%402x.png' mode='widthFix'></image>
        </view> -->
        <view class="right-msg-list-avatar-box">
          <image class="right-msg-list-avatar" src='https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F4582899c-cff4-4363-a04f-908be3513443%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1679795943&t=da989d03216a8a1f6ad8dc989399f1c0'></image>
        </view>
      </view>
      <!-- (左) -->
      <view wx:else id='msg-{{index}}' class="left-msg-list">
        <view style='width: 70rpx; height: 70rpx;margin-right: 30rpx;'>
          <image style='width: 70rpx; height: 70rpx; border-radius: 50%;' src='https://img1.baidu.com/it/u=4131860888,2773799558&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800'></image>
        </view>
        <!-- <view wx:if="{{ item.type == 'TIMTextElem' || item.type == 'TIMSoundElem' }}" style='width: 4vw; height: 11vw; margin-left: 16rpx; display: flex; align-items: center; z-index: 9;'>
          <image style='width: 4vw;' src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im---%E4%B8%89%E8%A7%92%E5%BD%A2%201%20%E6%8B%B7%E8%B4%9D%206%402x.png' mode='widthFix'></image>
        </view> -->
        <view class='leftMsg' wx:if="{{ item.type == 'TIMTextElem' }}">{{item.payload.text}}</view>
        <image bindtap="getLookBigImage" data-imgurl="{{ item.payload.imageInfoArray[0].imageUrl  }}" style="width: 300rpx;border-radius: 10rpx;" mode="widthFix" wx:if="{{ item.type == 'TIMImageElem' }}" src="{{ item.payload.imageInfoArray[0].imageUrl }}"></image>
        <view class="remoteAudioUrl" bindtap="innerAudioHandler" data-audio="{{ item.payload.remoteAudioUrl }}" style="justify-content: flex-start;padding-left: 30rpx;width: {{ item.payload.second*20 }}rpx;max-width: 650rpx;min-width: 132rpx;" wx:if="{{ item.type == 'TIMSoundElem' }}">
          <image src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230228%E8%AF%AD%E9%9F%B3%402x.png" mode="widthFix" style="width: 22rpx;" />
          <text style="color: #fff;margin-right: 20rpx;">{{ item.payload.second }}''</text>
        </view>
      </view>

    </block>
    <!-- </view> -->

    <!-- 占位 -->
    <view style='width: 100%; height: 18vw;'></view>
  </scroll-view>
  <!-- {{inputBottom}} -->
  <view class="inputRoom" style='bottom: {{ inputBottom }};height: {{ inputRoomHeight }};'>
    <view style="display: flex;align-items: center;box-sizing: border-box;padding: 20rpx;width: 100%;background-color: #000000;">
      <image style='width: 7vw;' bindtap="changeAudioHandler" data-flag="{{ true }}" wx:if="{{ !audioFlag }}" src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230224%E8%AF%AD%E9%9F%B3%20%281%29%402x.png' mode='widthFix'></image>
      <image style='width: 7vw;' bindtap="changeAudioHandler" data-flag="{{ false }}" wx:else src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230227%E9%94%AE%E7%9B%98%402x.png' mode='widthFix'></image>
      <input bindconfirm='sendClick' wx:if="{{ !audioFlag }}" adjust-position='{{false}}' model:value='{{inputVal}}' confirm-type='send' bindfocus='focus' bindblur='blur'></input>
      <view wx:else class="audio" bind:longpress="handleRecordStart" bind:touchmove="handleTouchMove" bind:touchend="handleRecordStop">
        <text>按住</text>
        <text>说话</text>
      </view>
      <image style='width: 7vw; margin-left: 3.2vw;' bindtap="changeEmoji" src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230224%E8%A1%A8%E6%83%85%402x.png' mode='widthFix'></image>
      <image style='width: 7vw; margin-left: 3.2vw;' bindtap="multimediaHandler" src='https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230224icon_%E6%B7%BB%E5%8A%A0%402x.png' mode='widthFix'></image>
    </view>
    <!-- 表情 -->
    <view wx:if="{{displayFlag === 'emoji'}}" class="TUI-Emoji-area">
      <Emoji bind:enterEmoji="appendMessage" />
    </view>
    <!-- 图片 -->
    <view wx:if="{{displayFlag === 'extension'}}" class="TUI-Extensions">
      <view class="TUI-Extension-slot" style="margin-left: 40rpx;" bindtap="handleSendImage">
        <view class="extension-box">
          <image class="TUI-Extension-icon" mode="widthFix" src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230227%E6%8B%8D%E6%91%84-%E9%80%89%E4%B8%AD%402x%20%282%29.png" />
        </view>
        <view class="TUI-Extension-slot-name">照片</view>
      </view>
      <view class="TUI-Extension-slot" style="margin-left: 90rpx;" bindtap="handleSendPicture">
        <view class="extension-box">
          <image class="TUI-Extension-icon" mode="widthFix" src="https://tongxuecool.oss-cn-beijing.aliyuncs.com/applet-tongxuecool/im-20230227%E6%8B%8D%E6%91%84-%E9%80%89%E4%B8%AD%402x%20%281%29.png" />
        </view>
        <view class="TUI-Extension-slot-name">拍摄</view>
      </view>
    </view>
  </view>

  <!-- 正在录音动效 -->
  <view class="record-modal" wx:if="{{recordFlag}}" bind:longpress="handleRecordStart" bind:touchmove="handleTouchMove" bind:touchend="handleRecordStop">
    <view class="wrapper">
      <view class="modal-loading">
      </view>
    </view>
    <view class="modal-title">
      正在录音
    </view>
  </view>
</view>

chitchat.wxss

/* pages/contact/contact.wxss */

page {
  background-color: #080808;
}

.msg-list-box {
  margin-top: 150rpx;
  background-color: #080808;
}

.nav-box {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  font-size: 30rpx;
  font-family: PingFang SC;
  font-weight: 500;
  color: #F5F5F5;
}

.inputRoom {
  box-sizing: border-box;
  width: 100vw;
  background-color: #000000;
  position: fixed;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  z-index: 20;
}

input {
  /* width: 76vw; */
  /* height: 9.33vw; */
  width: 518rpx;
  height: 64rpx;
  background-color: #333333;
  border-radius: 40rpx;
  margin-left: 2vw;
  padding: 0 3vw;
  font-size: 28rpx;
  color: #e6e6e6;
}

.right-msg-list-box {
  display: flex;
  justify-content: flex-end;
  padding: 3vw 3vw 3vw 11vw;
}

.right-msg-list-avatar-box {
  width: 70rpx;
  height: 70rpx;
  margin-left: 30rpx;
}

.right-msg-list-avatar {
  width: 70rpx;
  height: 70rpx;
  border-radius: 50%;
}

.left-msg-list {
  display: flex;
  padding: 3vw 11vw 3vw 3vw;
}

.leftMsg {
  font-size: 35rpx;
  color: #E6E6E6;
  line-height: 7vw;
  padding: 2vw 2.5vw;
  background: #1F1F1F;
  /* margin-left: -1.6vw; */
  border-radius: 10rpx;
  z-index: 10;
}

.rightMsg {
  font-size: 35rpx;
  color: #E6E6E6;
  line-height: 7vw;
  padding: 2vw 2.5vw;
  background: #1F1F1F;
  /* margin-right: -1.6vw; */
  border-radius: 10rpx;
  z-index: 10;
}

/* 表情 */
.TUI-Emoji-area {
  width: 100vw;
  height: 200px;
  background-color: #000000;
  padding-bottom: 100rpx;
}

/* 多媒体 */
.TUI-Extensions {
  display: flex;
  flex-wrap: wrap;
  width: 100vw;
  height: 400rpx;
  background-color: #000000;
}

.TUI-Extension-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 48rpx;
}

.extension-box {
  width: 100rpx;
  height: 100rpx;
  background: #333333;
  border-radius: 28rpx;
  display: flex;
  justify-content: center;
  align-items: center;
}

.TUI-Extension-icon {
  width: 51rpx;
  border-radius: 0;
}

.TUI-Extension-slot-name {
  font-size: 26rpx;
  font-family: PingFang SC;
  font-weight: 500;
  color: #B3B3B3;
  margin-top: 12rpx;
  text-align: center;
}

.message-time {
  font-size: 22rpx;
  font-family: PingFang SC;
  font-weight: 500;
  color: #CCCCCC;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 30rpx;
}

.audio {
  width: 518rpx;
  height: 64rpx;
  background-color: #333333;
  border-radius: 40rpx;
  margin-left: 2vw;
  padding: 0 3vw;
  font-size: 28rpx;
  color: #e6e6e6;
  display: flex;
  justify-content: center;
  align-items: center;
}

.audio:active {
  background-color: #5e5c5c;
}

/* 正在录音 */

.record-modal {
  height: 300rpx;
  width: 60vw;
  background-color: #333;
  opacity: 0.8;
  position: fixed;
  top: 670rpx;
  z-index: 9999;
  left: 20vw;
  border-radius: 24rpx;
  display: flex;
  flex-direction: column;
}

.record-modal .wrapper {
  display: flex;
  height: 200rpx;
  box-sizing: border-box;
  padding: 10vw;
}

.record-modal .wrapper .modal-loading {
  opacity: 1;
  width: 40rpx;
  height: 16rpx;
  border-radius: 4rpx;
  background-color: #006fff;
  animation: loading 2s cubic-bezier(0.17, 0.37, 0.43, 0.67) infinite;
}

.modal-title {
  text-align: center;
  color: #fff;
}

@keyframes loading {
  0% {
    transform: translate(0, 0)
  }

  50% {
    transform: translate(30vw, 0);
    background-color: #f5634a;
    width: 40px;
  }

  100% {
    transform: translate(0, 0);
  }
}

.remoteAudioUrl {
  box-sizing: border-box;
  height: 75rpx;
  background: #1F1F1F;
  border-radius: 10rpx;
  display: flex;
  align-items: center;
  font-size: 30rpx;
  color: #e6e6e6;
}

chitchat.js

import dayjs from './../../../../utils/dayjs'
const app = getApp();
var windowHeight = wx.getSystemInfoSync().windowHeight;
var keyHeight = 0;

/**
 * 计算msg总高度
 */
function calScrollHeight(that, keyHeight) {
  var query = wx.createSelectorQuery();
  query.select('.scrollMsg').boundingClientRect(function (rect) {}).exec();
}
const recorderManager = wx.getRecorderManager();

const innerAudioContext = wx.createInnerAudioContext({
  useWebAudioImplement: true
})

Page({

  /**
   * 页面的初始数据
   */
  data: {
    navbarTitle: "",
    audioFlag: false,
    messageTime: "",
    showMessageTime: false,
    inputRoomHeight: '16vw',
    scrollHeight: '',
    inputBottom: 0,
    toUserId: null, //被发送人的用户id
    messageList: [], //历史记录的消息列表
    displayFlag: '',
    inputVal: '',
    nextReqMessageID: null,
    isCompleted: false,
    recordFlag: false,
    recordOptions: {
      duration: 60000,
      sampleRate: 44100,
      numberOfChannels: 1,
      encodeBitRate: 192000,
      format: 'aac'
    },
  },
  changeEmoji() {
    this.setData({
      scrollHeight: '',
      inputBottom: 0,
      audioFlag: false
    })
    this.setData({
      displayFlag: 'emoji',
      inputBottom: '0px',
      inputRoomHeight: '200px'
    })
  },
  multimediaHandler() {
    this.setData({
      scrollHeight: '',
      inputBottom: 0,
    })
    this.setData({
      displayFlag: 'extension',
      inputBottom: '0px',
      inputRoomHeight: '200px'
    })
  },
  /**
   * 点击空白处
   * */
  clickScrollView() {
    this.setData({
      displayFlag: '',
      inputBottom: '0',
      inputRoomHeight: '16vw',
    })
  },
  /**
   * 语音切换
   * */
  changeAudioHandler(e) {
    let {
      flag
    } = e.currentTarget.dataset;
    this.setData({
      audioFlag: flag,
      inputRoomHeight: '16vw',
      displayFlag: ""
    })
  },
  /**
   * 语音播报
   * */
  innerAudioHandler(e) {
    let {
      audio
    } = e.currentTarget.dataset;
    innerAudioContext.src = audio;
    if (innerAudioContext.paused) {
      innerAudioContext.play();
    } else {
      innerAudioContext.stop();
    }
  },
  /**
   *  拍摄
   * @param sourceType 选择图片的来源
   * */
  handleSendPicture() {
    this.publicImageUpload('camera')
  },
  /**
   * 
   * 从相册选取
   * @param sourceType 选择图片的来源
   * 
   * */
  handleSendImage() {
    this.publicImageUpload('album')
  },
  /**
   * 
   * 选择图片和拍摄公共方法
   * 
   * */
  publicImageUpload(sourceType) {
    let {
      messageList
    } = this.data
    if (app.globalData.TIM.EVENT.SDK_READY == 'sdkStateReady') {
      wx.chooseImage({
        sourceType: [sourceType],
        count: 1,
        success: res => {
          wx.showLoading({
            title: '',
          })
          let message = app.globalData.tim.createImageMessage({
            to: 'zhanggenyuan',
            conversationType: app.globalData.TIM.TYPES.CONV_C2C,
            payload: {
              file: res
            },
            onProgress: event => {
              console.log('file uploading:', event)
              if (event == 1) {
                wx.hideLoading()
              }
            }
          });
          let promise = app.globalData.tim.sendMessage(message);
          promise.then(imResponse => {
            this.getMessageListHandler()
            this.clickScrollView()
          }).catch(imError => {
            console.warn('sendMessage error:', imError);
          });
        },
        complete: () => {
          wx.hideLoading()
        }
      })
    }

  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    console.log(options)
    this.setData({
      toUserId: options.toUserId,
      navbarTitle: "谢霆锋"
    })
    if (app.globalData.TIM.EVENT.SDK_READY == 'sdkStateReady') {
      this.getMessageListHandler(options.toUserId)
    }
    app.globalData.tim.on(app.globalData.TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived);
    setTimeout(() => {
      this.setData({
        toView: 'msg-' + (this.data.messageList.length - 1),
      })
    }, 800)
  },
  /**
   * 
   * 表情接收事件
   * 
   * */
  appendMessage(emoji) {
    let {
      message
    } = emoji.detail
    let {
      inputVal
    } = this.data
    this.setData({
      inputVal: inputVal + message
    })
  },
  /**
   * 语音相关
   * */
  handleRecordStart() {
    console.log("开始")
    let _this = this;
    wx.getSetting({
      success: (res) => {
        let auth = res.authSetting['scope.record']
        if (auth === false) {
          wx.openSetting({
            success: function (res) {
              let auth = res.authSetting['scope.record']
              if (auth === true) {
                wx.showToast({
                  title: '授权成功',
                  icon: 'success',
                  duration: 1500
                })
              } else {
                wx.showToast({
                  title: '授权失败',
                  icon: 'none',
                  duration: 1500
                })
              }
            }
          })
        } else if (auth === true) {
          _this.setData({
            recordFlag: true
          })
          recorderManager.start(this.data.recordOptions)
        } else {
          wx.authorize({
            scope: 'scope.record',
            success: () => {
              wx.showToast({
                title: '授权成功',
                icon: 'success',
                duration: 1500
              })
            }
          })
        }
      },
      fail: function () {
        wx.showToast({
          title: '授权失败',
          icon: 'none',
          duration: 1500
        })
      }
    })

  },
  handleTouchMove() {

  },
  handleRecordStop() {
    let _this = this
    _this.setData({
      recordFlag: false
    })
    recorderManager.stop()
    recorderManager.onStop(res => {
      console.log(res)
      if (app.globalData.TIM.EVENT.SDK_READY == 'sdkStateReady') {
        const message = app.globalData.tim.createAudioMessage({
          to: 'zhanggenyuan',
          conversationType: app.globalData.TIM.TYPES.CONV_C2C,
          payload: {
            file: res
          },
        });
        let promise = app.globalData.tim.sendMessage(message);
        promise.then(function (imResponse) {
          console.log(imResponse);
          _this.setData({
            messageList: [..._this.data.messageList, imResponse.data.message]
          })
          setTimeout(() => {
            _this.setData({
              toView: 'msg-' + (_this.data.messageList.length - 1),
            })
          }, 300)
        }).catch(function (imError) {
          console.warn('sendMessage error:', imError);
        });
      }

    })
  },
  /**
   * 
   * 监听接收消息事件
   * 
   * */
  onMessageReceived(event) {
    let {
      messageList
    } = this.data
    let _this = this
    const receiveMessageList = event.data;
    receiveMessageList.forEach((message) => {
      _this.messageTimeForShow(message)
      if (message.type === app.globalData.TIM.TYPES.MSG_TEXT) {
        // 文本消息
        _this.setData({
          messageList: [...messageList, message]
        })
      } else if (message.type === app.globalData.TIM.TYPES.MSG_IMAGE) {
        _this.setData({
          messageList: [...messageList, message]
        })
        // 图片消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_SOUND) {
        _this.setData({
          messageList: [...messageList, message]
        })
        // 音频消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_VIDEO) {
        // 视频消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_FILE) {
        // 文件消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_CUSTOM) {
        // 自定义消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_MERGER) {
        // 合并消息 
      } else if (message.type === app.globalData.TIM.TYPES.MSG_LOCATION) {
        // 地理位置消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_GRP_TIP) {
        // 群提示消息
      } else if (message.type === app.globalData.TIM.TYPES.MSG_GRP_SYS_NOTICE) {
        // 群系统通知
      }
      _this.setData({
        toView: 'msg-' + (_this.data.messageList.length - 1),
      })
    });
  },
  /**
   * 点击查看大图
   * @param Event
   * */
  getLookBigImage(event) {
    console.log(event)
    let {
      imgurl
    } = event.currentTarget.dataset
    console.log(imgurl)
    wx.previewImage({
      current: imgurl,
      urls: [imgurl]
    })
  },
  /**
   * 
   * 拉取会话历史记录
   * 
   * */
  getMessageListHandler() {
    console.log("进来了")
    let promise = app.globalData.tim.getMessageList({
      conversationID: `C2Czhanggenyuan`
    });
    promise.then((imResponse) => {
      const messageList = imResponse.data.messageList;
      const nextReqMessageID = imResponse.data.nextReqMessageID;
      const isCompleted = imResponse.data.isCompleted;
      this.setData({
        messageList: messageList,
        nextReqMessageID,
        isCompleted
      })
      console.log(messageList, imResponse.data, "历史会话")
      setTimeout(() => {
        this.setData({
          toView: 'msg-' + (this.data.messageList.length - 1),
        })
      }, 200)
    });
  },
  /**
   * 滚动到顶部
   * */
  bindscrolltoupperHandler() {

    if (app.globalData.TIM.EVENT.SDK_READY == 'sdkStateReady') {
      if (this.data.isCompleted) return
      console.log("进去到徐拉 ", this.data.messageList, this.data.nextReqMessageID)
      let _this = this;
      let promise = app.globalData.tim.getMessageList({
        conversationID: 'C2Czhanggenyuan',
        nextReqMessageID: _this.data.nextReqMessageID
      });
      promise.then(function (imResponse) {
        console.log(imResponse.data.messageList, imResponse.data, "********************")
        const messageList = imResponse.data.messageList;
        const nextReqMessageID = imResponse.data.nextReqMessageID;
        const isCompleted = imResponse.data.isCompleted;
        if (messageList.length == 0) {
          return
        }
        _this.setData({
          messageList: [...messageList, ..._this.data.messageList],
          nextReqMessageID,
          isCompleted
        })
      });
    }
  },
  /**
   *  发送消息
   * */
  sendClick() {
    let _this = this
    let {
      toUserId
    } = _this.data
    if (app.globalData.TIM.EVENT.SDK_READY == 'sdkStateReady') {
      let message = app.globalData.tim.createTextMessage({
        to: "zhanggenyuan",
        conversationType: app.globalData.TIM.TYPES.CONV_C2C,
        payload: {
          text: _this.data.inputVal
        },
      });
      // 2. 发送消息
      let promise = app.globalData.tim.sendMessage(message);
      promise.then(function (imResponse) {
        // 发送成功
        console.log(imResponse, "发送成功");
        _this.messageTimeForShow(imResponse.data.message)
        setTimeout(() => {
          _this.setData({
            inputVal: "",
            messageList: [..._this.data.messageList, imResponse.data.message]
          })
          _this.setData({
            toView: 'msg-' + (_this.data.messageList.length - 1),
          })
        }, 300)
      }).catch(function (imError) {
        // 发送失败
        console.warn('sendMessage error:', imError);
      });
    }
  },
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {},

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 获取聚焦
   */
  focus(e) {
    keyHeight = e.detail.height;
    this.setData({
      scrollHeight: (windowHeight - keyHeight) + 'px'
    });
    this.setData({
      toView: 'msg-' + (this.data.messageList.length - 1),
      inputBottom: keyHeight + 'px',
      displayFlag: "",
      inputRoomHeight: '16vw',
    })
  },

  //失去聚焦(软键盘消失)
  blur(e) {
    this.setData({
      scrollHeight: '',
      inputBottom: 0
    })
    this.setData({
      toView: 'msg-' + (this.data.messageList.length - 1),
      displayFlag: "",
    })

  },

  /**
   * 退回上一页
   */
  toBackClick: function () {
    wx.switchTab({
      url: '/pages/tabBar/appContainer/appContainer?pageidx=3',
    })
  },
  // 展示消息时间
  messageTimeForShow(messageTime) {
    const interval = 5 * 60 * 1000;
    const nowTime = Math.floor(messageTime.time / 10) * 10 * 1000;
    if (this.data.messageList.length > 0) {
      const lastTime = this.data.messageList.slice(-1)[0].time * 1000;
      if (nowTime - lastTime > interval) {
        this.data.messageTime = dayjs(nowTime);
        let timer = dayjs(nowTime).format('MM-DD HH:mm')
        let ts = timer.split(" ")
        let m = ts[0].split("-")[0]
        let d = ts[0].split("-")[1]
        messageTime.messageTime = `${m}月${d}日  ${ts[1]}`
        messageTime.showMessageTime = true
      } else {
        messageTime.showMessageTime = false
      }
    }
  },

})

emoji.wxml

<scroll-view scroll-y="true" enable-flex="true" class="TUI-Emoji">
  <view class="TUI-emoji-image" wx:for="{{emojiList}}" wx:key="index" >
    <view data-value="{{ item.value }}" bindtap="handleEnterEmoji" style="width: 100%;height: 100%;font-size: 48rpx;">
      {{ item.value }}
    </view>
  </view>
</scroll-view>

emoji.wxss

.TUI-Emoji {
  display: flex;
  justify-content: flex-start;
  flex-wrap: wrap;
  width: 100%;
  height: 100%;
  margin-left: 4vw;
}

.TUI-emoji-image {
  width: 9vw;
  height: 9vw;
  margin: 2vw;
}

.TUI-emoji-image > image {
  width: 100%;
  height: 100%;
}

emoji.js


import emoji from './../../../../../../utils/emoji'
Component({
  /**
   * 组件的属性列表
   */
  properties: {

  },

  /**
   * 组件的初始数据
   */
  data: {
    emojiList: emoji.emoji,
  },

  lifetimes: {
  },

  /**
   * 组件的方法列表
   */
  methods: {
    handleEnterEmoji(event) {
      this.triggerEvent('enterEmoji', {
        message: event.currentTarget.dataset.value,
      });
    },
  },
});

emoji.js文件

var emoji = [{
        name: "微笑",
        value: "😀"
    },
    {
        name: "呲牙",
        value: "😁"
    },
    {
        name: "撇嘴",
        value: "😐"
    },
    {
        name: "色",
        value: "😍"
    },
    {
        name: "发呆",
        value: "😳"
    },
    {
        name: "得意",
        value: "😎"
    },
    {
        name: "流泪",
        value: "😭"
    },
    {
        name: "害羞",
        value: "🙈"
    },
    {
        name: "闭嘴",
        value: "🙊"
    },
    {
        name: "睡觉",
        value: "💤"
    },
    {
        name: "大哭",
        value: "😭"
    },
    {
        name: "尴尬",
        value: "😂"
    },
    {
        name: "发怒",
        value: "😡"
    },
    {
        name: "调皮",
        value: "😜"
    },
    {
        name: "咄牙",
        value: "😁"
    },
    {
        name: "惊讶",
        value: "😮"
    },
    {
        name: "难过",
        value: "😔"
    },
    {
        name: "囹",
        value: "😂"
    },
    {
        name: "抓狂",
        value: "😀"
    },
    {
        name: "吐",
        value: "🤮"
    },
    {
        name: "偷笑",
        value: "😄"
    },
    {
        name: "愉快",
        value: "😀"
    },
    {
        name: "白眼",
        value: "🙄"
    },
    {
        name: "傲慢",
        value: "😀"
    },
    {
        name: "困",
        value: "🥱"
    },
    {
        name: "惊恐",
        value: "😨"
    },
    {
        name: "流汗",
        value: "💦"
    },
    {
        name: "憨笑",
        value: "😀"
    },
    {
        name: "悠闲",
        value: "🆓"
    },
    {
        name: "奋斗",
        value: "😀"
    },
    {
        name: "咒骂",
        value: "🤬"
    },
    {
        name: "疑问",
        value: "❓"
    },
    {
        name: "嘘",
        value: "🤫"
    },
    {
        name: "晕",
        value: "😵"
    },
    {
        name: "衰",
        value: "😞"
    },
    {
        name: "敲打",
        value: "🤛"
    },
    {
        name: "再见",
        value: "👋🏻"
    },
    {
        name: "擦汗",
        value: "💦"
    },
    {
        name: "抠鼻",
        value: "抠👃"
    },
    {
        name: "鼓掌",
        value: "👏🏻"
    },
    {
        name: "坏笑",
        value: "😀"
    },
    {
        name: "左哼哼",
        value: "👈"
    },
    {
        name: "右哼哼",
        value: "👉🏻"
    },
    {
        name: "鄙视",
        value: "🖕🏻"
    },
    {
        name: "委屈",
        value: "🥺"
    },
    {
        name: "快哭啦",
        value: "😭"
    },
    {
        name: "阴险",
        value: "⛅"
    },
    {
        name: "亲亲",
        value: "😙"
    },
    {
        name: "可怜",
        value: "🥺"
    },
    {
        name: "啤酒",
        value: "🍺"
    },
    {
        name: "咖啡",
        value: "☕"
    },
    {
        name: "猪头",
        value: "🐷"
    },
    {
        name: "凋谢",
        value: "🥺"
    },
    {
        name: "嘴唇",
        value: "🥴"
    },
    {
        name: "爱心",
        value: "❤"
    },
    {
        name: "心碎",
        value: "💔"
    },
    {
        name: "拥抱",
        value: "🤗"
    },
    {
        name: "强",
        value: "💪"
    },
    {
        name: "弱",
        value: "😭"
    },
    {
        name: "握手",
        value: "🤝"
    },
    {
        name: "胜利",
        value: "✌"
    },
    {
        name: "抱拳",
        value: "👊"
    },
    {
        name: "OK",
        value: "👌"
    },
    {
        name: "笑脸",
        value: "😀"
    },
    {
        name: "生病",
        value: "😷"
    },
    {
        name: "笑哭",
        value: "😂"
    },
    {
        name: "吐舌",
        value: "😛"
    },
    {
        name: "懵逼",
        value: "❓"
    },
    {
        name: "失望",
        value: "😞"
    },
    {
        name: "呵呵",
        value: "🙂"
    },
    {
        name: "嘿哈",
        value: "😀"
    },
    {
        name: "捂脸",
        value: "🤦"
    },
    {
        name: "奸笑",
        value: "😼"
    },
    {
        name: "机智",
        value: "😀"
    },
    {
        name: "皱眉",
        value: "🙍"
    },
    {
        name: "耶",
        value: "✌"
    },
    {
        name: "鬼脸",
        value: "🤪"
    },
    {
        name: "双手合十",
        value: "🙏"
    },
    {
        name: "加油",
        value: "⛽"
    },
    {
        name: "礼花",
        value: "🎉"
    },
    {
        name: "礼物",
        value: "🧧"
    },
    {
        name: "开心",
        value: "🤤"
    },
    {
        name: "自拍",
        value: "🤳"
    },
    {
        name: "叹气",
        value: "🤫"
    },
    {
        name: "爱",
        value: "❤️"
    },
    {
        name: "寿司",
        value: "🍣"
    },
    {
        name: "火锅",
        value: "🍲"
    },
    {
        name: "汉堡",
        value: "🍔"
    },
    {
        name: "牛排",
        value: "🥩"
    },
    {
        name: "面包",
        value: "🍞"
    },
    {
        name: "爆米花",
        value: "🍿"
    },
    {
        name: "糖果",
        value: "🍬"
    },
    {
        name: "拔草",
        value: "☘︎"
    },
    {
        name: "种草",
        value: "🍀"
    },
    {
        name: "一人食",
        value: "🍜"
    },
    {
        name: "鸭子",
        value: "🦆"
    },
    {
        name: "K歌",
        value: "🎤"
    },
    {
        name: "婴儿",
        value: "👶"
    },
    {
        name: "指甲油",
        value: "💅"
    },
    {
        name: "化妆镜",
        value: "🔭"
    },
    {
        name: "口红",
        value: "💄"
    },
    {
        name: "钻石",
        value: "💎"
    },
    {
        name: "发廊",
        value: "💇🏼"
    },
    {
        name: "吹风机",
        value: "🌬️"
    },
]

module.exports = {
    emoji
}

dayjs 下载下来的压缩源码 有需要的自行下载

!(function (t, e) {
  'object' === typeof exports && 'undefined' !== typeof module ? module.exports = e() : 'function' === typeof define && define.amd ? define(e) : (t = 'undefined' !== typeof globalThis ? globalThis : t || self).dayjs = e();
}(this, (() => {
  'use strict';const t = 1e3; const e = 6e4; const n = 36e5; const r = 'millisecond'; const i = 'second'; const s = 'minute'; const u = 'hour'; const a = 'day'; const o = 'week'; const f = 'month'; const h = 'quarter'; const c = 'year'; const d = 'date'; const $ = 'Invalid Date'; const l = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/; const y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g; const M = { name: 'en', weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_') }; const m = function (t, e, n) {
    const r = String(t);return !r || r.length >= e ? t : `${Array(e + 1 - r.length).join(n)}${t}`;
  }; const g = { s: m, z(t) {
    const e = -t.utcOffset(); const n = Math.abs(e); const r = Math.floor(n / 60); const i = n % 60;return `${(e <= 0 ? '+' : '-') + m(r, 2, '0')}:${m(i, 2, '0')}`;
  }, m: function t(e, n) {
    if (e.date() < n.date()) return -t(n, e);const r = 12 * (n.year() - e.year()) + (n.month() - e.month()); const i = e.clone().add(r, f); const s = n - i < 0; const u = e.clone().add(r + (s ? -1 : 1), f);return +(-(r + (n - i) / (s ? i - u : u - i)) || 0);
  }, a(t) {
    return t < 0 ? Math.ceil(t) || 0 : Math.floor(t);
  }, p(t) {
    return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t] || String(t || '').toLowerCase()
      .replace(/s$/, '');
  }, u(t) {
    return void 0 === t;
  } }; let D = 'en'; const v = {};v[D] = M;const p = function (t) {
    return t instanceof _;
  }; const S = function (t, e, n) {
    let r;if (!t) return D;if ('string' === typeof t)v[t] && (r = t), e && (v[t] = e, r = t);else {
      const i = t.name;v[i] = t, r = i;
    } return !n && r && (D = r), r || !n && D;
  }; const w = function (t, e) {
    if (p(t)) return t.clone();const n = 'object' === typeof e ? e : {};return n.date = t, n.args = arguments, new _(n);
  }; const O = g;O.l = S, O.i = p, O.w = function (t, e) {
    return w(t, { locale: e.$L, utc: e.$u, x: e.$x, $offset: e.$offset });
  };
  var _ = (function () {
    function M(t) {
      this.$L = S(t.locale, null, !0), this.parse(t);
    } const m = M.prototype;return m.parse = function (t) {
      this.$d = (function (t) {
        const e = t.date; const n = t.utc;if (null === e) return new Date(NaN);if (O.u(e)) return new Date;if (e instanceof Date) return new Date(e);if ('string' === typeof e && !/Z$/i.test(e)) {
          const r = e.match(l);if (r) {
            const i = r[2] - 1 || 0; const s = (r[7] || '0').substring(0, 3);return n ? new Date(Date.UTC(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s)) : new Date(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s);
          }
        } return new Date(e);
      }(t)), this.$x = t.x || {}, this.init();
    }, m.init = function () {
      const t = this.$d;this.$y = t.getFullYear(), this.$M = t.getMonth(), this.$D = t.getDate(), this.$W = t.getDay(), this.$H = t.getHours(), this.$m = t.getMinutes(), this.$s = t.getSeconds(), this.$ms = t.getMilliseconds();
    }, m.$utils = function () {
      return O;
    }, m.isValid = function () {
      return !(this.$d.toString() === $);
    }, m.isSame = function (t, e) {
      const n = w(t);return this.startOf(e) <= n && n <= this.endOf(e);
    }, m.isAfter = function (t, e) {
      return w(t) < this.startOf(e);
    }, m.isBefore = function (t, e) {
      return this.endOf(e) < w(t);
    }, m.$g = function (t, e, n) {
      return O.u(t) ? this[e] : this.set(n, t);
    }, m.unix = function () {
      return Math.floor(this.valueOf() / 1e3);
    }, m.valueOf = function () {
      return this.$d.getTime();
    }, m.startOf = function (t, e) {
      const n = this; const r = !!O.u(e) || e; const h = O.p(t); const $ = function (t, e) {
        const i = O.w(n.$u ? Date.UTC(n.$y, e, t) : new Date(n.$y, e, t), n);return r ? i : i.endOf(a);
      }; const l = function (t, e) {
        return O.w(n.toDate()[t].apply(n.toDate('s'), (r ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)), n);
      }; const y = this.$W; const M = this.$M; const m = this.$D; const g = `set${this.$u ? 'UTC' : ''}`;switch (h) {
        // eslint-disable-next-line no-var
        case c:return r ? $(1, 0) : $(31, 11);case f:return r ? $(1, M) : $(0, M + 1);case o:var D = this.$locale().weekStart || 0; var v = (y < D ? y + 7 : y) - D;return $(r ? m - v : m + (6 - v), M);case a:case d:return l(`${g}Hours`, 0);case u:return l(`${g}Minutes`, 1);case s:return l(`${g}Seconds`, 2);case i:return l(`${g}Milliseconds`, 3);default:return this.clone();
      }
    }, m.endOf = function (t) {
      return this.startOf(t, !1);
    }, m.$set = function (t, e) {
      let n; const o = O.p(t); const h = `set${this.$u ? 'UTC' : ''}`; const $ = (n = {}, n[a] = `${h}Date`, n[d] = `${h}Date`, n[f] = `${h}Month`, n[c] = `${h}FullYear`, n[u] = `${h}Hours`, n[s] = `${h}Minutes`, n[i] = `${h}Seconds`, n[r] = `${h}Milliseconds`, n)[o]; const l = o === a ? this.$D + (e - this.$W) : e;if (o === f || o === c) {
        const y = this.clone().set(d, 1);y.$d[$](l), y.init(), this.$d = y.set(d, Math.min(this.$D, y.daysInMonth())).$d;
      } else $ && this.$d[$](l);return this.init(), this;
    }, m.set = function (t, e) {
      return this.clone().$set(t, e);
    }, m.get = function (t) {
      return this[O.p(t)]();
    }, m.add = function (r, h) {
      let d; const $ = this;r = Number(r);const l = O.p(h); const y = function (t) {
        const e = w($);return O.w(e.date(e.date() + Math.round(t * r)), $);
      };if (l === f) return this.set(f, this.$M + r);if (l === c) return this.set(c, this.$y + r);if (l === a) return y(1);if (l === o) return y(7);const M = (d = {}, d[s] = e, d[u] = n, d[i] = t, d)[l] || 1; const m = this.$d.getTime() + r * M;return O.w(m, this);
    }, m.subtract = function (t, e) {
      return this.add(-1 * t, e);
    }, m.format = function (t) {
      const e = this; const n = this.$locale();if (!this.isValid()) return n.invalidDate || $;const r = t || 'YYYY-MM-DDTHH:mm:ssZ'; const i = O.z(this); const s = this.$H; const u = this.$m; const a = this.$M; const o = n.weekdays; const f = n.months; const h = function (t, n, i, s) {
        return t && (t[n] || t(e, r)) || i[n].substr(0, s);
      }; const c = function (t) {
        return O.s(s % 12 || 12, t, '0');
      }; const d = n.meridiem || function (t, _e, n) {
        const r = t < 12 ? 'AM' : 'PM';return n ? r.toLowerCase() : r;
      }; const l = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a + 1, MM: O.s(a + 1, 2, '0'), MMM: h(n.monthsShort, a, f, 3), MMMM: h(f, a), D: this.$D, DD: O.s(this.$D, 2, '0'), d: String(this.$W), dd: h(n.weekdaysMin, this.$W, o, 2), ddd: h(n.weekdaysShort, this.$W, o, 3), dddd: o[this.$W], H: String(s), HH: O.s(s, 2, '0'), h: c(1), hh: c(2), a: d(s, u, !0), A: d(s, u, !1), m: String(u), mm: O.s(u, 2, '0'), s: String(this.$s), ss: O.s(this.$s, 2, '0'), SSS: O.s(this.$ms, 3, '0'), Z: i };return r.replace(y, ((t, e) => e || l[t] || i.replace(':', '')));
    }, m.utcOffset = function () {
      return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
    }, m.diff = function (r, d, $) {
      let l; const y = O.p(d); const M = w(r); const m = (M.utcOffset() - this.utcOffset()) * e; const g = this - M; let D = O.m(this, M);return D = (l = {}, l[c] = D / 12, l[f] = D, l[h] = D / 3, l[o] = (g - m) / 6048e5, l[a] = (g - m) / 864e5, l[u] = g / n, l[s] = g / e, l[i] = g / t, l)[y] || g, $ ? D : O.a(D);
    }, m.daysInMonth = function () {
      return this.endOf(f).$D;
    }, m.$locale = function () {
      return v[this.$L];
    }, m.locale = function (t, e) {
      if (!t) return this.$L;const n = this.clone(); const r = S(t, e, !0);return r && (n.$L = r), n;
    }, m.clone = function () {
      return O.w(this.$d, this);
    }, m.toDate = function () {
      return new Date(this.valueOf());
    }, m.toJSON = function () {
      return this.isValid() ? this.toISOString() : null;
    }, m.toISOString = function () {
      return this.$d.toISOString();
    }, m.toString = function () {
      return this.$d.toUTCString();
    }, M;
  }()); const b = _.prototype;return w.prototype = b, [['$ms', r], ['$s', i], ['$m', s], ['$H', u], ['$W', a], ['$M', f], ['$y', c], ['$D', d]].forEach(((t) => {
    b[t[1]] = function (e) {
      return this.$g(e, t[0], t[1]);
    };
  })), w.extend = function (t, e) {
    return t.$i || (t(e, _, w), t.$i = !0), w;
  }, w.locale = S, w.isDayjs = p, w.unix = function (t) {
    return w(1e3 * t);
  }, w.en = v[D], w.Ls = v, w.p = {}, w;
})));

app.js

/**
 * 
 *  腾讯云IM
 * 
 * */
import TIM from 'tim-wx-sdk';
import TIMUploadPlugin from 'tim-upload-plugin';
import TIMProfanityFilterPlugin from 'tim-profanity-filter-plugin';
import {
  genTestUserSig
} from './utils/debug/GenerateTestUserSig';
/**
     * 
     * 腾讯云IM
     * 
     * */

    const config = {
      userID: '3099', //User ID
      SDKAPPID: xxxxx, // Your SDKAppID
      SECRETKEY: 'xxxxxx', // Your secretKey
      EXPIRETIME: 604800,
    }
    let options = {
      SDKAppID: config.SDKAPPID
    };
    let tim = TIM.create(options);
    tim.setLogLevel(0);

    // 注册腾讯云即时通信 IM 上传插件
    tim.registerPlugin({
      'tim-upload-plugin': TIMUploadPlugin
    });

    // 注册腾讯云即时通信 IM 本地审核插件
    tim.registerPlugin({
      'tim-profanity-filter-plugin': TIMProfanityFilterPlugin
    });

    const userSig = genTestUserSig(config).userSig
    let promise = tim.login({
      userID: config.userID,
      userSig
    });
    promise.then((imResponse) => {
      console.log(imResponse.data, "登录");
      if (imResponse.data.repeatLogin === true) {
        console.log(imResponse.data.errorInfo);
      }
    }).catch(function (imError) {
      console.warn('login error:', imError);
    });

    this.globalData.tim = tim
    this.globalData.TIM = TIM

目前只是初步实现功能 正在做相关优化 记录一下 毕竟同一个功能不想写两遍 [捂眼] 懒人一个