Skip to content

互动消息聊天指南

本文介绍互动消息相关功能的使用方法,包括消息收发、历史消息、消息撤回和私聊。

发送消息

文本消息

typescript
const result = await classroom.sendTextMessage('大家好!');
if (!result.ok) {
  console.error('发送失败:', result.message);
}

图片消息

typescript
// 选择文件后发送(自动上传 COS)
const fileInput = document.querySelector<HTMLInputElement>('#image-input');
const file = fileInput?.files?.[0];
if (file) {
  const result = await classroom.sendImageMessage(file);
  if (!result.ok) console.error('图片发送失败:', result.message);
}

文件消息

typescript
const file = fileInput.files[0];
const result = await classroom.sendFileMessage(file);
if (!result.ok) {
  console.error('文件发送失败:', result.message);
}

自定义消息

typescript
// 发送自定义消息(content 会被序列化为 JSON 字符串)
const result = await classroom.sendCustomMessage({
  type: 'emoji',
  emojiId: 'thumbs_up',
});

私聊消息

typescript
// 发送定向私聊消息(仅目标用户可见)
const result = await classroom.sendDirectedMessage('user_002', '你好,有问题想请教');
if (!result.ok) {
  console.error('私聊发送失败:', result.message);
}

接收消息

通过响应式状态订阅消息列表

订阅完整消息列表(包含历史消息 + 实时新消息)

typescript
classroom.state.messageList$.subscribe(messages => {
  renderMessageList(messages);
});

通过事件监听新消息

监听新消息(用于触发通知/滚动等 UI 操作)

typescript
import { TEvent } from '@tencent-classroom/sdk';

classroom.on(TEvent.RECV_MESSAGE, (message) => {
  console.log('新消息:', message.content);
  scrollToBottom();
  showNotification(message);
});

解析展示私聊消息标识

messageList$ 中包含公聊消息 + 与自己相关的私聊消息。

私聊消息通过 message.ext(即 IM 的 cloudCustomData)中的字段标识:

typescript
interface MessageExt {
  IsPrivateMsg?: boolean;      // 是否为私聊消息
  PrivateInfo?: {
    From: { ID: string };      // 发送者 userId
    To: { ID: string };        // 接收者 userId
  };
}

解析示例:

typescript
classroom.state.messageList$.subscribe((messages) => {
  for (const msg of messages) {
    // 解析 ext 字段
    const ext = typeof msg.ext === 'string' ? JSON.parse(msg.ext || '{}') : (msg.ext || {});

    if (ext.IsPrivateMsg) {
      // 这是一条私聊消息
      const fromId = ext.PrivateInfo?.From?.ID;
      const toId = ext.PrivateInfo?.To?.ID;
      renderPrivateMessage(msg, fromId, toId);
    } else {
      // 普通公聊消息
      renderPublicMessage(msg);
    }
  }
});

messageList$ 的过滤规则

SDK 内部已确保 messageList$ 只包含:

  • 所有公聊消息(Text / Image / File 类型)
  • 与当前用户相关的私聊消息(自己是发送者 From 或接收者 To)

其他用户之间的私聊消息不会出现在列表中,业务层无需再做可见性判断。

历史消息

SDK 提供三个互补的历史消息 API,覆盖向上分页向下渐进分页一键回到最新三种场景。进房后 SDK 会自动加载课堂历史,业务层无需手动触发。

API实现路径用途
loadOlderMessages(count)TIM SDK getMessageListHoppingdirection=0向上分页加载更早的消息
loadNewerMessages(count)TIM SDK getMessageListHoppingdirection=1向下渐进分页加载更新消息(适合滚到底部继续浏览)
seekToLatestMessages()TIM SDK getMessageList 循环拉取约 100 条 + 整批 replace messageList$一键回到最新(适合点击"↓ N 条新消息"按钮)

messageList$ 内置 500 条上限,触发双向裁剪

  • append(实时 / loadNewerMessages)超限时从头部裁剪 → state.hasOlderMessages$=true
  • prependloadOlderMessages)超限时从尾部裁剪 → state.hasNewerMessages$=true(进入"历史浏览"模式)

双向滑动窗口状态

状态类型含义
state.hasOlderMessages$boolean是否还有更早消息可加载(向上分页锚点)
state.hasNewerMessages$boolean是否处于"历史浏览"模式(实时消息会被静默吸收)
state.newerMessagesCount$number历史浏览态期间被吸收的新消息数(不含 Tips)

历史浏览态下的发送行为

hasNewerMessages$=true 时,5 个用户主动发送 API(sendTextMessage / sendImageMessage / sendFileMessage / sendCustomMessage / sendDirectedMessage)会先自动 await seekToLatestMessages() 再插入乐观消息,确保自己的消息追加到与"实时尾"一致的列表。

向上分页(loadOlderMessages

typescript
// UI 滚动到顶部时拉取更早的消息(基于 messageList$ 头部最老消息的 seq)
const result = await classroom.loadOlderMessages(15);
if (result.ok && result.data.length === 0) {
  console.log('已无更早消息');
}

调用条件

loadOlderMessages 需要 messageList$ 非空才能取到 seq 锚点;进房后 SDK 已自动加载初始历史,无需业务层干预。

向下渐进分页(loadNewerMessages

typescript
// UI 滚动到底部时调用(基于 messageList$ 末尾最新消息的 seq),
// 让用户继续向下浏览被裁掉的更新消息,渐进追上实时尾。
const result = await classroom.loadNewerMessages(15);
if (result.ok && result.data.length === 0) {
  console.log('已是最新');
}

一键回到最新(seekToLatestMessages

typescript
// 用户点击"↓ N 条新消息"浮动按钮时调用。
// SDK 循环 getMessageList 拉取约 100 条最新消息后整批 replace messageList$,
// 同时清零 hasNewerMessages$ / newerMessagesCount$。
if (classroom.state.hasNewerMessages$.get()) {
  const r = await classroom.seekToLatestMessages();
  if (r.ok) scrollChatToBottom();
}

滚动驱动的完整示例

typescript
const msgListEl = document.querySelector('.chat-list')!;
let loadingOlder = false;
let loadingNewer = false;

msgListEl.addEventListener('scroll', async () => {
  // 滚到顶部:分页加载更早消息(仅在仍可能有更早时尝试)
  if (msgListEl.scrollTop <= 0 && !loadingOlder && classroom.state.hasOlderMessages$.get()) {
    loadingOlder = true;
    const prevHeight = msgListEl.scrollHeight;
    const res = await classroom.loadOlderMessages(15);
    loadingOlder = false;
    if (res.ok) {
      // 还原 scrollTop,避免视觉跳动
      msgListEl.scrollTop = msgListEl.scrollHeight - prevHeight;
    }
  }

  // 滚到底部 + 处于历史浏览态:渐进追上实时尾
  const distanceToBottom = msgListEl.scrollHeight - msgListEl.clientHeight - msgListEl.scrollTop;
  if (distanceToBottom <= 8 && !loadingNewer && classroom.state.hasNewerMessages$.get()) {
    loadingNewer = true;
    await classroom.loadNewerMessages(15);
    loadingNewer = false;
  }
});

// 浮动"↓ N 条新消息"按钮:一键回到最新
classroom.state.hasNewerMessages$.subscribe((show) => {
  toggleBackToLatestButton(show, classroom.state.newerMessagesCount$.get());
});
classroom.state.newerMessagesCount$.subscribe((n) => updateBackToLatestBadge(n));
backToLatestButton.addEventListener('click', () => classroom.seekToLatestMessages());

消息撤回

typescript
// 撤回自己的消息(所有角色可撤回自己的消息)
const result = await classroom.revokeMessage(message.id);
if (!result.ok) {
  console.error('撤回失败:', result.message);
}

// 老师/助教撤回他人消息(按 IM 序列号)
await classroom.revokeClassMessage(message.seq);

监听消息撤回事件:

typescript
classroom.on(TEvent.MESSAGE_REVOKED, ({ msgId }) => {
  console.log('消息已被撤回:', msgId);
});

未读管理

typescript
// 订阅未读消息数
classroom.state.messageUnreadCount$.subscribe(count => {
  updateBadge(count);
});

// 标记全部已读
classroom.markAllMessagesAsRead();

// 标记指定序列号以前的消息为已读
classroom.markMessageAsRead(latestSeq);

聊天模式控制

老师/助教可控制课堂聊天模式:

typescript
// 设置聊天模式
await classroom.setSilenceMode('freeChat');      // 自由聊天
await classroom.setSilenceMode('publicOnly');    // 仅公开消息
await classroom.setSilenceMode('privateOnly');   // 仅私聊
await classroom.setSilenceMode('muteAll');       // 全员禁言

// 订阅禁言模式变化
classroom.state.silenceMode$.subscribe(mode => {
  console.log('当前聊天模式:', mode);
});

检查发送权限

typescript
// 检查当前用户是否可发送消息
if (classroom.canSendChatMessage()) {
  // 可以发送
} else {
  showToast('当前处于禁言状态');
}

相关 API