腾讯云即时通信 IM SDK 完整使用指南
前言
最近在新公司做了直播项目,以前接触过lviekit,所以做起来还算啥得心应手,因为以前公司的聊天服务,都是自己封装websocket去做的,新公司是直接去接腾讯IM,所以记录一下,接入的话,也比较简单
📋 文档概述
这是一份基于项目代码的腾讯云 IM SDK 完整使用指南。涵盖从环境搭建、API 详解到高级功能实现的所有内容。
🎯 适用场景
- 🎬 直播应用: 直播间聊天、礼物系统、主播通话
- 💬 即时通讯: 单聊、群聊、文件传输
- 📞 音视频通话: 语音/视频通话信令管理
- 🎮 游戏社交: 游戏内聊天、组队沟通
- 🏢 企业办公: 内部沟通、会议协作
📑 完整目录
基础篇
API 篇
- [TIMService 完整API](#TIMService 完整API "#5-timservice-%E5%AE%8C%E6%95%B4api")
- [IMStore 状态管理](#IMStore 状态管理 "#6-imstore-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86")
- 消息类型系统
- 事件系统详解
高级篇
1. 环境要求与安装
1.1 系统环境要求
bash
# Node.js 版本要求
Node.js >= 16.0.0
# 包管理器
npm >= 7.0.0 或 yarn >= 1.22.0 或 pnpm >= 6.0.0
# 浏览器支持
Chrome >= 60
Firefox >= 60
Safari >= 12
Edge >= 79
1.2 腾讯云 IM 控制台配置
步骤 1: 创建应用
- 访问 腾讯云 IM 控制台
- 点击"创建新应用"
- 填写应用名称,选择数据中心
- 记录生成的
SDKAppID
步骤 2: 配置基础信息
javascript
// 在控制台获取以下信息
const imConfig = {
SDKAppID: "your_appId", // 你的应用 ID
SecretKey: 'your_secret', // 用于生成 UserSig
AdminUserID: 'admin' // 管理员账号
}
步骤 3: 域名白名单配置
bash
# 开发环境
http://localhost:3000
http://localhost:8080
# 生产环境
https://yourdomain.com
https://www.yourdomain.com
1.3 依赖包安装
核心依赖
bash
# 腾讯云 IM SDK
npm install @tencentcloud/chat
# 文件上传插件
npm install tim-upload-plugin
# 状态管理
npm install pinia
# 事件总线
npm install mitt
开发依赖
bash
# TypeScript 支持
npm install -D typescript @types/node
# 构建工具
npm install -D vite @vitejs/plugin-vue
# 代码规范
npm install -D eslint prettier
UI 框架(可选)
bash
# Varlet UI(项目中使用)
npm install @varlet/ui
# Element Plus
npm install element-plus
# Ant Design Vue
npm install ant-design-vue
2. 项目配置详解
2.1 目录结构规划
bash
src/
├── config/
│ ├── tim.ts # TIM SDK 配置
│ ├── constants.ts # 常量定义
│ └── environment.ts # 环境配置
├── utils/
│ ├── timService.ts # TIM 服务封装
│ ├── request.ts # HTTP 请求
│ ├── storage.ts # 本地存储
│ └── validation.ts # 数据验证
├── stores/
│ ├── im.ts # IM 状态管理
│ ├── user.ts # 用户状态
│ ├── conversation.ts # 会话状态
│ └── message.ts # 消息状态
├── types/
│ ├── tim.d.ts # TIM 类型定义
│ ├── api.d.ts # API 类型定义
│ └── common.d.ts # 通用类型
├── components/
│ ├── Chat/ # 聊天组件
│ ├── Message/ # 消息组件
│ └── Common/ # 通用组件
└── api/
├── im.ts # IM 相关 API
├── user.ts # 用户 API
└── upload.ts # 文件上传 API
2.2 配置文件详解
2.2.1 TIM 基础配置
typescript
// src/config/tim.ts
export interface TIMConfig {
SDKAppID: number
logLevel: number
timeout: {
login: number
message: number
invite: number
}
retry: {
maxRetries: number
retryDelay: number
}
features: {
translation: boolean
fileUpload: boolean
signaling: boolean
}
}
export const timConfig: TIMConfig = {
// 基础配置
SDKAppID: Number(import.meta.env.VITE_TIM_SDK_APP_ID) || 1400430140,
// 日志级别
// 0: 普通级别,日志量较少
// 1: 详细级别,日志量较多,推荐开发调试时使用
// 2: 告警级别,只有告警和错误日志
// 3: 错误级别,只有错误日志
// 4: 无日志级别,不输出任何日志
logLevel: import.meta.env.DEV ? 1 : 3,
// 超时配置
timeout: {
login: 10000, // 登录超时(毫秒)
message: 10000, // 消息发送超时
invite: 30000 // 信令邀请超时
},
// 重试配置
retry: {
maxRetries: 3, // 最大重试次数
retryDelay: 1000 // 重试延迟(毫秒)
},
// 功能开关
features: {
translation: true, // 消息翻译功能
fileUpload: true, // 文件上传功能
signaling: true // 信令功能
}
}
// 消息类型配置
export const messageTypes = {
// 显示的消息类型
DISPLAY_TYPES: [
'TIMTextElem', // 文本消息
'TIMImageElem', // 图片消息
'TIMSoundElem', // 语音消息
'TIMVideoFileElem', // 视频消息
'TIMFileElem', // 文件消息
'TIMCustomElem' // 自定义消息
],
// 自定义消息业务类型
CUSTOM_BUSINESS_IDS: {
GIFT: 'gift', // 礼物消息
LOCATION: 'location', // 位置消息
VOICE_CALL: 'voice_call', // 语音通话
VIDEO_CALL: 'video_call', // 视频通话
RECHARGE: 'ask_recharge', // 充值请求
OFFICIAL: 'official_message', // 官方消息
DYNAMIC_EMOJI: 'dynamic_emoji' // 动态表情
}
}
// 群组配置
export const groupConfig = {
// 群组类型
TYPES: {
WORK: 'Work', // 好友工作群
PUBLIC: 'Public', // 陌生人社交群
MEETING: 'Meeting', // 会议群
AVCHATROOM: 'AVChatRoom' // 直播群
},
// 加群方式
JOIN_OPTIONS: {
FREE: 'FreeAccess', // 自由加入
NEED_PERMISSION: 'NeedPermission', // 需要验证
DISABLE_APPLY: 'DisableApply' // 禁止加群
},
// 群成员角色
MEMBER_ROLES: {
OWNER: 'Owner', // 群主
ADMIN: 'Admin', // 管理员
MEMBER: 'Member' // 普通成员
}
}
2.2.2 环境配置
bash
# .env.development
VITE_TIM_SDK_APP_ID=1400430140
VITE_TIM_LOG_LEVEL=1
VITE_API_BASE_URL=http://localhost:3000
VITE_UPLOAD_BASE_URL=http://localhost:3001
# .env.production
VITE_TIM_SDK_APP_ID=1400430140
VITE_TIM_LOG_LEVEL=3
VITE_API_BASE_URL=https://api.yourdomain.com
VITE_UPLOAD_BASE_URL=https://upload.yourdomain.com
# .env.test
VITE_TIM_SDK_APP_ID=1400430140
VITE_TIM_LOG_LEVEL=2
VITE_API_BASE_URL=https://test-api.yourdomain.com
2.2.3 TypeScript 类型定义
typescript
// src/types/tim.d.ts
// 扩展全局 TIM 类型
declare module '@tencentcloud/chat' {
namespace TIM {
interface Message {
ID: string
type: string
payload: any
conversationID: string
from: string
to: string
time: number
status: 'unSend' | 'success' | 'fail'
sendStatus?: 'pending' | 'success' | 'fail'
businessStatus?: 'translated' | 'recalled'
nick?: string
avatar?: string
nameCard?: string
}
interface Conversation {
conversationID: string
type: 'C2C' | 'GROUP'
lastMessage: Message
unreadCount: number
isPinned: boolean
userProfile?: UserProfile
groupProfile?: GroupProfile
}
interface UserProfile {
userID: string
nick: string
avatar: string
selfSignature: string
gender: 'TIM_GENDER_MALE' | 'TIM_GENDER_FEMALE' | 'TIM_GENDER_UNKNOWN'
birthday: number
location: string
language: number
level: number
role: number
allowType: 'TIM_FRIEND_ALLOW_ANY' | 'TIM_FRIEND_NEED_CONFIRM' | 'TIM_FRIEND_DENY_ANY'
}
interface GroupProfile {
groupID: string
name: string
avatar: string
type: string
introduction: string
notification: string
ownerID: string
createTime: number
memberCount: number
maxMemberCount: number
joinOption: string
muteAllMembers: boolean
}
}
}
// 业务类型定义
export interface CustomMessagePayload {
businessID: string
type?: number
data?: any
timestamp: number
}
export interface GiftMessageData extends CustomMessagePayload {
businessID: 'gift'
type: 3
giftId: number
giftName: string
giftCount: number
giftValue: number
}
export interface CallInviteData extends CustomMessagePayload {
businessID: 'video_call' | 'voice_call'
actionType: 1 | 2 | 3 | 4 | 5 // 邀请|取消|接受|拒绝|超时
callType: 'voice' | 'video'
roomId: string
inviter: string
invitee: string
}
export interface LocationMessageData extends CustomMessagePayload {
businessID: 'location'
latitude: number
longitude: number
address: string
description?: string
}
// API 响应类型
export interface APIResponse<T = any> {
code: number
message: string
data: T
}
export interface LoginResponse {
userID: string
userSig: string
token: string
expires: number
}
// Store 状态类型
export interface IMStoreState {
// 连接状态
isSDKReady: boolean
isLogin: boolean
connectionState: 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
// 用户信息
userInfo: {
userID: string
userSig: string
profile?: TIM.UserProfile
} | null
// 会话相关
conversationList: TIM.Conversation[]
currentConversationID: string | null
// 消息相关
messageList: Record<string, TIM.Message[]>
unreadCount: number
// 群组相关
memberCount: number
groupOnlineMemberList: any[]
anchorInRoom: boolean
anchorUserID: string | null
// 信令相关
activeInvitation: any | null
targetGroupId: string | null
}
3. 核心架构设计
3.1 整体架构图
scss
┌─────────────────────────────────────────────────────────────┐
│ Vue 3 应用层 │
├─────────────────────────────────────────────────────────────┤
│ Chat Components │ Message Components │ UI Components │
├─────────────────────────────────────────────────────────────┤
│ Pinia 状态管理层 │
├─────────────────────────────────────────────────────────────┤
│ IMStore │ UserStore │ ConversationStore │
├─────────────────────────────────────────────────────────────┤
│ 业务逻辑封装层 │
├─────────────────────────────────────────────────────────────┤
│ TIMService (单例服务) │
├─────────────────────────────────────────────────────────────┤
│ SDK 适配层 │
├─────────────────────────────────────────────────────────────┤
│ TIM SDK │ Upload Plugin │ Event System │ Utils │
└─────────────────────────────────────────────────────────────┘
3.2 TIMService 单例设计
typescript
// src/utils/timService.ts
import TIM from '@tencentcloud/chat'
import TIMUploadPlugin from 'tim-upload-plugin'
import { timConfig } from '@/config/tim'
class TIMService {
private static instance: TIMService | null = null
public tim: ReturnType<typeof TIM.create>
private isInitialized: boolean = false
private constructor() {
this.initializeSDK()
}
/**
* 获取单例实例
*/
public static getInstance(): TIMService {
if (!TIMService.instance) {
TIMService.instance = new TIMService()
}
return TIMService.instance
}
/**
* 初始化 SDK
*/
private initializeSDK() {
if (this.isInitialized) return
try {
// 创建 SDK 实例
this.tim = TIM.create({
SDKAppID: timConfig.SDKAppID
})
// 设置日志级别
this.tim.setLogLevel(timConfig.logLevel)
// 注册上传插件
this.tim.registerPlugin({
'tim-upload-plugin': TIMUploadPlugin
})
this.isInitialized = true
console.log('✅ TIM SDK 初始化成功')
} catch (error) {
console.error('❌ TIM SDK 初始化失败:', error)
throw error
}
}
/**
* 检查 SDK 是否已初始化
*/
public isReady(): boolean {
return this.isInitialized && !!this.tim
}
/**
* 销毁 SDK 实例
*/
public destroy() {
if (this.tim) {
this.tim.destroy()
this.tim = null as any
}
this.isInitialized = false
TIMService.instance = null
}
}
// 导出单例实例
export default TIMService.getInstance()
3.3 状态管理架构
typescript
// src/stores/im.ts
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import TIM from '@tencentcloud/chat'
import timService from '@/utils/timService'
import type { IMStoreState } from '@/types/tim'
export const useIMStore = defineStore('im', () => {
// ===== 状态定义 =====
// 连接状态
const isSDKReady = ref(false)
const isLogin = ref(false)
const connectionState = ref<'connecting' | 'connected' | 'disconnected' | 'reconnecting'>('disconnected')
// 用户信息
const userInfo = ref<IMStoreState['userInfo']>(null)
// 会话相关
const conversationList = ref<TIM.Conversation[]>([])
const currentConversationID = ref<string | null>(null)
// 消息相关
const messageList = ref<Record<string, TIM.Message[]>>({})
const unreadCount = ref(0)
// 群组相关
const memberCount = ref(0)
const groupOnlineMemberList = ref<any[]>([])
const anchorInRoom = ref(false)
const anchorUserID = ref<string | null>(null)
// 信令相关
const activeInvitation = ref<any | null>(null)
const targetGroupId = ref<string | null>(null)
// ===== 计算属性 =====
// 当前会话消息
const currentMessages = computed(() => {
if (!currentConversationID.value) return []
return messageList.value[currentConversationID.value] || []
})
// 总未读消息数
const totalUnreadCount = computed(() => {
return conversationList.value.reduce((total, conv) => {
return total + conv.unreadCount
}, 0)
})
// 置顶会话列表
const pinnedConversations = computed(() => {
return conversationList.value
.filter(conv => conv.isPinned)
.sort((a, b) => b.lastMessage.time - a.lastMessage.time)
})
// 普通会话列表
const normalConversations = computed(() => {
return conversationList.value
.filter(conv => !conv.isPinned)
.sort((a, b) => b.lastMessage.time - a.lastMessage.time)
})
// ===== 监听器 =====
// 监听当前会话变化,自动标记已读
watch([currentConversationID, isSDKReady], ([convID, sdkReady]) => {
if (convID && sdkReady && isLogin.value) {
markMessageAsRead(convID)
}
})
// 监听连接状态变化
watch(connectionState, (newState) => {
console.log(`🔌 连接状态变化: ${newState}`)
if (newState === 'connected') {
// 连接成功后的处理
updateUnreadCount()
loadConversationList()
} else if (newState === 'disconnected') {
// 连接断开后的处理
console.warn('⚠️ IM 连接已断开')
}
})
// ===== 核心方法 =====
/**
* 初始化事件监听
*/
function initializeEventListeners() {
// SDK 就绪事件
timService.on(TIM.EVENT.SDK_READY, handleSDKReady)
// 消息接收事件
timService.on(TIM.EVENT.MESSAGE_RECEIVED, handleMessageReceived)
// 会话更新事件
timService.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, handleConversationUpdate)
// 网络状态变化
timService.on(TIM.EVENT.NET_STATE_CHANGE, handleNetworkStateChange)
// 被踢下线
timService.on(TIM.EVENT.KICKED_OUT, handleKickedOut)
// 信令事件监听
initializeSignalingListeners()
}
/**
* 销毁事件监听
*/
function destroyEventListeners() {
timService.off(TIM.EVENT.SDK_READY, handleSDKReady)
timService.off(TIM.EVENT.MESSAGE_RECEIVED, handleMessageReceived)
timService.off(TIM.EVENT.CONVERSATION_LIST_UPDATED, handleConversationUpdate)
timService.off(TIM.EVENT.NET_STATE_CHANGE, handleNetworkStateChange)
timService.off(TIM.EVENT.KICKED_OUT, handleKickedOut)
destroySignalingListeners()
}
// ===== 导出 =====
return {
// 状态
isSDKReady,
isLogin,
connectionState,
userInfo,
conversationList,
currentConversationID,
messageList,
unreadCount,
memberCount,
groupOnlineMemberList,
anchorInRoom,
anchorUserID,
activeInvitation,
targetGroupId,
// 计算属性
currentMessages,
totalUnreadCount,
pinnedConversations,
normalConversations,
// 方法
initializeEventListeners,
destroyEventListeners,
// ... 其他方法
}
})
4. 快速上手指南
4.1 最小化集成示例
步骤 1: 创建基础 Vue 应用
typescript
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
步骤 2: 创建聊天组件
vue
<!-- ChatDemo.vue -->
<template>
<div class="chat-demo">
<!-- 登录区域 -->
<div v-if="!imStore.isLogin" class="login-section">
<h3>IM 登录</h3>
<input v-model="loginForm.userID" placeholder="用户 ID" />
<input v-model="loginForm.userSig" placeholder="用户签名" />
<button @click="handleLogin" :disabled="isLogging">
{{ isLogging ? '登录中...' : '登录' }}
</button>
</div>
<!-- 聊天区域 -->
<div v-else class="chat-section">
<div class="chat-header">
<span>当前用户: {{ imStore.userInfo?.userID }}</span>
<button @click="handleLogout">登出</button>
</div>
<!-- 消息列表 -->
<div class="message-list" ref="messageListRef">
<div
v-for="message in imStore.currentMessages"
:key="message.ID"
class="message-item"
:class="{ 'my-message': isMyMessage(message) }"
>
<div class="message-info">
<span class="sender">{{ message.nick || message.from }}</span>
<span class="time">{{ formatTime(message.time) }}</span>
</div>
<!-- 文本消息 -->
<div v-if="message.type === 'TIMTextElem'" class="text-content">
{{ message.payload.text }}
</div>
<!-- 图片消息 -->
<div v-else-if="message.type === 'TIMImageElem'" class="image-content">
<img :src="message.payload.imageInfoArray[1].url" alt="图片" />
</div>
<!-- 消息状态 -->
<div v-if="isMyMessage(message)" class="message-status">
<span v-if="message.sendStatus === 'pending'">⏳</span>
<span v-else-if="message.sendStatus === 'success'">✅</span>
<span v-else-if="message.sendStatus === 'fail'" @click="resendMessage(message)">❌</span>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="input-section">
<input
v-model="messageText"
@keyup.enter="sendMessage"
placeholder="输入消息..."
:disabled="!targetUserID"
/>
<input v-model="targetUserID" placeholder="接收方 ID" />
<button @click="sendMessage" :disabled="!messageText.trim() || !targetUserID">
发送
</button>
<!-- 文件上传 -->
<input
type="file"
ref="fileInputRef"
@change="handleFileSelect"
accept="image/*"
style="display: none"
/>
<button @click="$refs.fileInputRef.click()">📷</button>
</div>
</div>
<!-- 状态指示器 -->
<div class="status-bar">
<span :class="connectionClass">{{ connectionText }}</span>
<span v-if="imStore.unreadCount > 0" class="unread-count">
未读: {{ imStore.unreadCount }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick, watch, onMounted, onUnmounted } from 'vue'
import { useIMStore } from '@/stores/im'
// Store
const imStore = useIMStore()
// 响应式数据
const loginForm = ref({
userID: 'testUser' + Math.floor(Math.random() * 1000),
userSig: ''
})
const messageText = ref('')
const targetUserID = ref('testUser2')
const isLogging = ref(false)
const messageListRef = ref<HTMLElement>()
const fileInputRef = ref<HTMLInputElement>()
// 计算属性
const connectionClass = computed(() => {
const state = imStore.connectionState
return {
'status-connected': state === 'connected',
'status-connecting': state === 'connecting',
'status-disconnected': state === 'disconnected',
'status-reconnecting': state === 'reconnecting'
}
})
const connectionText = computed(() => {
const stateMap = {
connected: '已连接',
connecting: '连接中',
disconnected: '已断开',
reconnecting: '重连中'
}
return stateMap[imStore.connectionState] || '未知状态'
})
// 方法
async function handleLogin() {
if (!loginForm.value.userID || !loginForm.value.userSig) {
alert('请填写用户 ID 和用户签名')
return
}
isLogging.value = true
try {
await imStore.login(loginForm.value.userID, loginForm.value.userSig)
console.log('✅ 登录成功')
// 设置当前会话
if (targetUserID.value) {
imStore.setCurrentConversation(`C2C${targetUserID.value}`)
}
} catch (error) {
console.error('❌ 登录失败:', error)
alert('登录失败: ' + error.message)
} finally {
isLogging.value = false
}
}
async function handleLogout() {
try {
await imStore.logout()
console.log('✅ 登出成功')
} catch (error) {
console.error('❌ 登出失败:', error)
}
}
async function sendMessage() {
if (!messageText.value.trim() || !targetUserID.value) return
try {
await imStore.sendMessage(targetUserID.value, messageText.value, 'C2C')
messageText.value = ''
// 滚动到底部
await nextTick()
scrollToBottom()
} catch (error) {
console.error('❌ 发送消息失败:', error)
alert('发送失败: ' + error.message)
}
}
async function handleFileSelect(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0]
if (!file || !targetUserID.value) return
try {
await imStore.sendImageMessage(targetUserID.value, file, 'C2C')
// 清空文件选择
if (fileInputRef.value) {
fileInputRef.value.value = ''
}
await nextTick()
scrollToBottom()
} catch (error) {
console.error('❌ 发送图片失败:', error)
alert('发送图片失败: ' + error.message)
}
}
async function resendMessage(message: any) {
try {
await imStore.resendMessage(message)
console.log('✅ 重发消息成功')
} catch (error) {
console.error('❌ 重发消息失败:', error)
}
}
function isMyMessage(message: any): boolean {
return message.from === imStore.userInfo?.userID
}
function formatTime(timestamp: number): string {
return new Date(timestamp * 1000).toLocaleTimeString()
}
function scrollToBottom() {
if (messageListRef.value) {
messageListRef.value.scrollTop = messageListRef.value.scrollHeight
}
}
// 监听消息变化,自动滚动
watch(() => imStore.currentMessages, () => {
nextTick(() => scrollToBottom())
}, { deep: true })
// 监听目标用户变化,切换会话
watch(targetUserID, (newUserID) => {
if (newUserID && imStore.isLogin) {
imStore.setCurrentConversation(`C2C${newUserID}`)
}
})
// 生命周期
onMounted(() => {
// 尝试自动登录
imStore.tryAutoLogin().catch(() => {
console.log('自动登录失败,需要手动登录')
})
})
onUnmounted(() => {
// 清理会话
imStore.setCurrentConversation('')
})
</script>
<style scoped>
.chat-demo {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.login-section {
text-align: center;
}
.login-section input {
display: block;
width: 100%;
margin: 10px 0;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.login-section button {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.login-section button:disabled {
background: #ccc;
cursor: not-allowed;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.message-list {
height: 400px;
overflow-y: auto;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.message-item {
margin-bottom: 15px;
padding: 10px;
border-radius: 8px;
background: #f5f5f5;
}
.message-item.my-message {
background: #007bff;
color: white;
margin-left: 20%;
}
.message-info {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 5px;
opacity: 0.7;
}
.text-content {
word-break: break-word;
}
.image-content img {
max-width: 200px;
border-radius: 4px;
}
.message-status {
text-align: right;
margin-top: 5px;
}
.message-status span {
cursor: pointer;
font-size: 14px;
}
.input-section {
display: flex;
gap: 10px;
padding: 10px 0;
}
.input-section input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.input-section button {
padding: 8px 15px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.input-section button:disabled {
background: #ccc;
cursor: not-allowed;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
font-size: 12px;
}
.status-connected { color: #28a745; }
.status-connecting { color: #ffc107; }
.status-disconnected { color: #dc3545; }
.status-reconnecting { color: #17a2b8; }
.unread-count {
background: #dc3545;
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
}
</style>
步骤 3: 在主应用中使用
vue
<!-- App.vue -->
<template>
<div id="app">
<h1>腾讯 IM SDK 演示</h1>
<ChatDemo />
</div>
</template>
<script setup lang="ts">
import ChatDemo from './components/ChatDemo.vue'
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
padding: 20px;
}
</style>
4.2 获取 UserSig 的方法
方法 1: 控制台生成(开发测试用)
- 登录腾讯云 IM 控制台
- 进入应用
- 点击"开发辅助工具" → "UserSig生成&校验"
- 输入用户标识符,点击生成
方法 2: 服务端生成(生产推荐)
javascript
// Node.js 示例
const TLSSigAPIv2 = require('tls-sig-api-v2')
const api = new TLSSigAPIv2.Api(SDKAppID, secretKey)
// 生成 UserSig,有效期 7 天
const userSig = api.genUserSig(userID, 7 * 24 * 3600)
python
# Python 示例
from TLSSigAPIv2 import TLSSigAPIv2
api = TLSSigAPIv2(SDKAppID, secretKey)
# 生成 UserSig,有效期 7 天
userSig = api.gen_user_sig(userID, 7 * 24 * 3600)
方法 3: 前端临时生成(仅开发测试)
typescript
// 注意:密钥不能暴露在前端代码中,此方法仅供开发测试
import { genTestUserSig } from '@/utils/generateTestUserSig'
const { userSig } = genTestUserSig(userID)
5. TIMService 完整API
5.1 认证相关 API
login(userID: string, userSig: string): Promise<void>
功能描述: 用户登录腾讯云 IM 系统,建立与服务器的连接。
详细参数:
-
userID
(string): 用户唯一标识符- 长度限制: 1-32 字符
- 字符限制: 字母、数字、下划线
- 区分大小写
- 示例:
'user123'
,'anchor_001'
,'customer_service'
-
userSig
(string): 用户身份验证签名- 由 App 服务端使用密钥生成
- 包含用户 ID、过期时间等信息
- 建议有效期设为 7 天
- 格式: Base64 编码的 JSON Web Token
返回值: Promise<void>
- 登录成功时 resolve,失败时 reject 并抛出错误
可能的错误码:
typescript
interface LoginError {
code: number
message: string
}
// 常见错误码
const LOGIN_ERRORS = {
2000: '参数错误',
3050: '用户已登录,无需重复登录',
6014: '用户 ID 不合法',
6015: 'SDKAppID 不匹配',
70001: 'UserSig 已过期',
70002: 'UserSig 长度为 0',
70003: 'UserSig 格式错误',
70005: 'UserSig 验证失败',
70009: 'UserSig 验证失败,请使用官网工具重新生成',
70013: '用户 ID 包含敏感词',
70014: '用户 ID 不能为空',
70016: '用户 ID 长度超限',
70020: 'SDKAppID 未找到,请在腾讯云即时通信 IM 控制台创建应用'
}
完整使用示例:
typescript
import timService from '@/utils/timService'
async function performLogin(userID: string, userSig: string) {
// 参数验证
if (!userID || !userSig) {
throw new Error('用户 ID 和 UserSig 不能为空')
}
if (userID.length > 32) {
throw new Error('用户 ID 长度不能超过 32 个字符')
}
if (!/^[a-zA-Z0-9_]+$/.test(userID)) {
throw new Error('用户 ID 只能包含字母、数字和下划线')
}
try {
console.log(`🔐 开始登录,用户 ID: ${userID}`)
await timService.login(userID, userSig)
console.log('✅ 登录成功')
// 登录成功后的处理
await postLoginSetup()
} catch (error) {
console.error('❌ 登录失败:', error)
// 错误处理
handleLoginError(error)
throw error
}
}
async function postLoginSetup() {
// 获取用户资料
const userProfile = await timService.getCurrentUser()
console.log('👤 用户资料:', userProfile.data)
// 获取会话列表
const conversations = await timService.getConversationList()
console.log('💬 会话列表:', conversations.data.conversationList)
}
function handleLoginError(error: any) {
const errorMessages: Record<number, string> = {
3050: '您已经登录,无需重复操作',
70001: 'UserSig 已过期,请重新获取',
70005: 'UserSig 验证失败,请检查签名是否正确',
70020: 'SDKAppID 不存在,请检查配置'
}
const message = errorMessages[error.code] || error.message || '登录失败'
// 显示用户友好的错误信息
showErrorMessage(message)
// 特殊错误处理
switch (error.code) {
case 70001:
// UserSig 过期,可以尝试自动刷新
refreshUserSigAndRetry()
break
case 70020:
// SDKAppID 错误,需要检查配置
console.error('请检查 timConfig.SDKAppID 配置是否正确')
break
}
}
async function refreshUserSigAndRetry() {
try {
// 从服务端获取新的 UserSig
const newUserSig = await fetchUserSigFromServer()
// 重新登录
await performLogin(getCurrentUserID(), newUserSig)
} catch (error) {
console.error('自动刷新 UserSig 失败:', error)
}
}
logout(): Promise<void>
功能描述: 用户登出 IM 系统,断开与服务器的连接并清理本地数据。
执行流程:
- 向服务器发送登出请求
- 断开 WebSocket 连接
- 清理本地缓存的消息和会话数据
- 停止接收新消息推送
- 清理事件监听器
返回值: Promise<void>
- 登出成功时 resolve,失败时 reject
注意事项:
- 登出后需要重新登录才能使用 IM 功能
- 本地存储的 UserSig 会被清除
- 正在进行的文件上传会被取消
完整使用示例:
typescript
async function performLogout() {
try {
console.log('🚪 开始登出...')
// 保存当前状态(可选)
await saveCurrentState()
// 执行登出
await timService.logout()
console.log('✅ 登出成功')
// 登出后的清理工作
await postLogoutCleanup()
} catch (error) {
console.error('❌ 登出失败:', error)
// 即使登出失败,也要执行本地清理
await postLogoutCleanup()
throw error
}
}
async function saveCurrentState() {
// 保存草稿消息
const draftMessages = getDraftMessages()
localStorage.setItem('draftMessages', JSON.stringify(draftMessages))
// 保存其他需要持久化的状态
}
async function postLogoutCleanup() {
// 清理敏感数据
localStorage.removeItem('userID')
localStorage.removeItem('userSig')
// 清理会话数据
sessionStorage.clear()
// 重置应用状态
resetApplicationState()
// 跳转到登录页面
navigateToLogin()
}
function resetApplicationState() {
// 重置 Pinia store
const imStore = useIMStore()
imStore.reset()
// 清理其他全局状态
}
getCurrentUser(): Promise<UserProfileResult>
功能描述: 获取当前登录用户的详细资料信息。
返回值类型:
typescript
interface UserProfileResult {
code: number
data: {
userID: string // 用户 ID
nick: string // 昵称
avatar: string // 头像 URL
selfSignature: string // 个性签名
gender: 'TIM_GENDER_MALE' | 'TIM_GENDER_FEMALE' | 'TIM_GENDER_UNKNOWN'
birthday: number // 生日时间戳(秒)
location: string // 位置信息
language: number // 语言
level: number // 等级
role: number // 角色
allowType: 'TIM_FRIEND_ALLOW_ANY' | 'TIM_FRIEND_NEED_CONFIRM' | 'TIM_FRIEND_DENY_ANY'
customInfo: Record<string, string> // 自定义字段
}
}
详细使用示例:
typescript
async function loadUserProfile() {
try {
const result = await timService.getCurrentUser()
const profile = result.data
console.log('👤 用户资料:')
console.log('用户 ID:', profile.userID)
console.log('昵称:', profile.nick || '未设置')
console.log('头像:', profile.avatar || '默认头像')
console.log('个性签名:', profile.selfSignature || '这个人很懒,什么都没写')
console.log('性别:', getGenderText(profile.gender))
console.log('生日:', formatBirthday(profile.birthday))
console.log('位置:', profile.location || '未设置')
// 更新 UI 显示
updateUserProfileUI(profile)
return profile
} catch (error) {
console.error('❌ 获取用户资料失败:', error)
// 使用默认资料
const defaultProfile = getDefaultProfile()
updateUserProfileUI(defaultProfile)
throw error
}
}
function getGenderText(gender: string): string {
const genderMap = {
'TIM_GENDER_MALE': '男',
'TIM_GENDER_FEMALE': '女',
'TIM_GENDER_UNKNOWN': '未设置'
}
return genderMap[gender] || '未知'
}
function formatBirthday(timestamp: number): string {
if (!timestamp) return '未设置'
const date = new Date(timestamp * 1000)
return date.toLocaleDateString('zh-CN')
}
function updateUserProfileUI(profile: any) {
// 更新头像
const avatarEl = document.querySelector('.user-avatar') as HTMLImageElement
if (avatarEl) {
avatarEl.src = profile.avatar || '/default-avatar.png'
}
// 更新昵称
const nickEl = document.querySelector('.user-nick')
if (nickEl) {
nickEl.textContent = profile.nick || profile.userID
}
// 更新个性签名
const signatureEl = document.querySelector('.user-signature')
if (signatureEl) {
signatureEl.textContent = profile.selfSignature || '这个人很懒,什么都没写'
}
}
function getDefaultProfile() {
return {
userID: 'unknown',
nick: '未知用户',
avatar: '/default-avatar.png',
selfSignature: '',
gender: 'TIM_GENDER_UNKNOWN',
birthday: 0,
location: '',
language: 0,
level: 0,
role: 0,
allowType: 'TIM_FRIEND_NEED_CONFIRM'
}
}
5.2 消息相关 API
createTextMessage(to: string, text: string, conversationType?: ConversationType): Message
功能描述: 创建文本消息对象,用于发送纯文本内容。
参数详解:
to
(string): 接收方标识符- 单聊: 对方的 userID,如
'user123'
- 群聊: 群组的 groupID,如
'group456'
- 单聊: 对方的 userID,如
text
(string): 消息文本内容- 最大长度: 12KB (约 12,000 个字符)
- 支持 Unicode 字符,包括 Emoji 表情
- 支持换行符
\n
- 自动过滤敏感词(如果配置了敏感词过滤)
conversationType
(ConversationType): 会话类型,可选参数TIM.TYPES.CONV_C2C
: 单聊会话(默认)TIM.TYPES.CONV_GROUP
: 群聊会话
返回值: 返回一个 Message 对象,包含以下主要属性:
typescript
interface TextMessage {
ID: string // 消息 ID(本地生成)
type: 'TIMTextElem' // 消息类型
payload: {
text: string // 消息文本
}
conversationID: string // 会话 ID
to: string // 接收方 ID
from: string // 发送方 ID(当前用户)
time: number // 消息时间戳
status: 'unSend' // 初始状态
sendStatus?: 'pending' // 发送状态(自定义字段)
}
详细使用示例:
typescript
// 基础文本消息创建
function createBasicTextMessage() {
const message = timService.createTextMessage(
'receiver123',
'你好!这是一条测试消息 😊'
)
console.log('📝 创建的消息:', message)
return message
}
// 创建群聊文本消息
function createGroupTextMessage() {
const message = timService.createTextMessage(
'liveRoom456',
'大家好!欢迎来到直播间 🎉',
TIM.TYPES.CONV_GROUP
)
return message
}
// 创建多行文本消息
function createMultilineMessage() {
const multilineText = `这是第一行
这是第二行
这是第三行
这是空行后的内容`
const message = timService.createTextMessage(
'user789',
multilineText
)
return message
}
// 创建包含特殊字符的消息
function createSpecialCharMessage() {
const specialText = `特殊字符测试:
🎵 音乐符号
💖 爱心表情
🌟 星星
👍 点赞
@用户名 提及用户
#话题标签 话题
http://example.com 链接`
const message = timService.createTextMessage(
'user456',
specialText
)
return message
}
// 文本长度验证
function createValidatedTextMessage(to: string, text: string) {
// 验证文本长度
const maxLength = 12 * 1024 // 12KB
const textBytes = new TextEncoder().encode(text).length
if (textBytes > maxLength) {
throw new Error(`消息长度超限,当前: ${textBytes} 字节,最大: ${maxLength} 字节`)
}
// 验证是否为空
if (!text.trim()) {
throw new Error('消息内容不能为空')
}
// 创建消息
const message = timService.createTextMessage(to, text)
// 添加自定义属性
message.sendStatus = 'pending'
message.createTime = Date.now()
return message
}
// 批量创建消息
function createBatchMessages(to: string, texts: string[]) {
return texts.map(text => {
const message = timService.createTextMessage(to, text)
message.batchId = Date.now() + Math.random()
return message
})
}
// 创建回复消息
function createReplyMessage(to: string, replyText: string, originalMessage: any) {
const replyContent = `回复 @${originalMessage.from}: ${replyText}`
const message = timService.createTextMessage(to, replyContent)
// 添加回复信息
message.replyTo = {
messageID: originalMessage.ID,
sender: originalMessage.from,
content: originalMessage.payload.text.substring(0, 50) + '...'
}
return message
}
消息内容处理工具:
typescript
// 消息内容处理工具类
class MessageContentProcessor {
// 过滤敏感词
static filterSensitiveWords(text: string): string {
const sensitiveWords = ['敏感词1', '敏感词2'] // 实际项目中从服务端获取
let filteredText = text
sensitiveWords.forEach(word => {
const regex = new RegExp(word, 'gi')
filteredText = filteredText.replace(regex, '*'.repeat(word.length))
})
return filteredText
}
// 提取 @ 提及
static extractMentions(text: string): string[] {
const mentionRegex = /@([a-zA-Z0-9_]+)/g
const mentions: string[] = []
let match
while ((match = mentionRegex.exec(text)) !== null) {
mentions.push(match[1])
}
return mentions
}
// 提取话题标签
static extractHashtags(text: string): string[] {
const hashtagRegex = /#([^\s#]+)/g
const hashtags: string[] = []
let match
while ((match = hashtagRegex.exec(text)) !== null) {
hashtags.push(match[1])
}
return hashtags
}
// 提取 URL 链接
static extractUrls(text: string): string[] {
const urlRegex = /(https?:\/\/[^\s]+)/g
const urls: string[] = []
let match
while ((match = urlRegex.exec(text)) !== null) {
urls.push(match[1])
}
return urls
}
// 格式化消息内容
static formatMessageContent(text: string): {
originalText: string
filteredText: string
mentions: string[]
hashtags: string[]
urls: string[]
hasSpecialContent: boolean
} {
const filteredText = this.filterSensitiveWords(text)
const mentions = this.extractMentions(text)
const hashtags = this.extractHashtags(text)
const urls = this.extractUrls(text)
return {
originalText: text,
filteredText,
mentions,
hashtags,
urls,
hasSpecialContent: mentions.length > 0 || hashtags.length > 0 || urls.length > 0
}
}
}
// 使用示例
function createEnhancedTextMessage(to: string, text: string) {
// 处理消息内容
const processed = MessageContentProcessor.formatMessageContent(text)
// 创建消息
const message = timService.createTextMessage(to, processed.filteredText)
// 添加扩展信息
message.mentions = processed.mentions
message.hashtags = processed.hashtags
message.urls = processed.urls
message.hasSpecialContent = processed.hasSpecialContent
console.log('📝 增强消息创建完成:', {
mentions: processed.mentions,
hashtags: processed.hashtags,
urls: processed.urls
})
return message
}
createCustomMessage(to: string, payload: CustomPayload, conversationType?: ConversationType, priority?: MessagePriority): Message
功能描述: 创建自定义消息对象,用于传输业务相关的结构化数据,如礼物、位置、音视频通话邀请等。
参数详解:
-
to
(string): 接收方标识符 -
payload
(CustomPayload): 自定义消息载荷typescriptinterface CustomPayload { data: string // 自定义数据(必填),通常为 JSON 字符串 description?: string // 消息描述(可选),用于消息列表显示 extension?: string // 扩展字段(可选),用于版本控制等 }
-
conversationType
(ConversationType): 会话类型 -
priority
(MessagePriority): 消息优先级TIM.TYPES.MSG_PRIORITY_HIGH
: 高优先级,优先传输TIM.TYPES.MSG_PRIORITY_NORMAL
: 普通优先级(默认)TIM.TYPES.MSG_PRIORITY_LOW
: 低优先级
业务消息类型定义:
typescript
// 礼物消息
interface GiftMessageData {
businessID: 'gift'
type: 3 // 消息类型标识
giftId: number // 礼物 ID
giftName: string // 礼物名称
giftIcon: string // 礼物图标
giftCount: number // 礼物数量
giftValue: number // 礼物价值
animation?: string // 动画效果
timestamp: number // 时间戳
}
// 位置消息
interface LocationMessageData {
businessID: 'location'
latitude: number // 纬度
longitude: number // 经度
address: string // 详细地址
description?: string // 位置描述
thumbnail?: string // 地图缩略图
timestamp: number
}
// 音视频通话邀请
interface CallInviteData {
businessID: 'video_call' | 'voice_call'
actionType: 1 | 2 | 3 | 4 | 5 // 1:邀请 2:取消 3:接受 4:拒绝 5:超时
callType: 'voice' | 'video' // 通话类型
roomId: string // 房间 ID
inviter: string // 邀请者 ID
invitee: string // 被邀请者 ID
duration?: number // 通话时长(结束时)
timestamp: number
}
// 充值请求消息
interface RechargeRequestData {
businessID: 'ask_recharge'
amount: number // 请求充值金额
currency: string // 货币类型
reason?: string // 充值原因
timestamp: number
}
// 官方消息
interface OfficialMessageData {
businessID: 'official_message'
messageType: 'system' | 'promotion' | 'announcement'
title: string // 消息标题
content: {
text?: string // 文本内容
image?: string // 图片 URL
uri?: string // 跳转链接
action?: string // 动作类型
}
priority: 'high' | 'normal' | 'low'
timestamp: number
}
详细使用示例:
typescript
// 1. 创建礼物消息
async function sendGiftMessage(receiverID: string, giftInfo: any) {
const giftData: GiftMessageData = {
businessID: 'gift',
type: 3,
giftId: giftInfo.id,
giftName: giftInfo.name,
giftIcon: giftInfo.icon,
giftCount: giftInfo.count,
giftValue: giftInfo.value,
animation: giftInfo.animation,
timestamp: Date.now()
}
const payload = {
data: JSON.stringify(giftData),
description: `${giftInfo.name} x${giftInfo.count}`,
extension: 'gift_v2.0'
}
try {
const message = timService.createCustomMessage(
receiverID,
payload,
TIM.TYPES.CONV_C2C,
TIM.TYPES.MSG_PRIORITY_HIGH // 礼物消息使用高优先级
)
console.log('🎁 礼物消息创建成功:', message)
// 发送消息
const result = await timService.sendMessage(message)
// 触发礼物动画
triggerGiftAnimation(giftData)
return result
} catch (error) {
console.error('❌ 礼物消息发送失败:', error)
throw error
}
}
// 2. 创建位置分享消息
async function sendLocationMessage(receiverID: string, locationInfo: any) {
const locationData: LocationMessageData = {
businessID: 'location',
latitude: locationInfo.lat,
longitude: locationInfo.lng,
address: locationInfo.address,
description: locationInfo.description,
thumbnail: await generateMapThumbnail(locationInfo.lat, locationInfo.lng),
timestamp: Date.now()
}
const payload = {
data: JSON.stringify(locationData),
description: `位置: ${locationInfo.address}`,
extension: 'location_v1.0'
}
const message = timService.createCustomMessage(
receiverID,
payload,
TIM.TYPES.CONV_C2C
)
return await timService.sendMessage(message)
}
// 3. 创建音视频通话邀请
async function sendCallInvitation(inviteeID: string, callType: 'voice' | 'video') {
const callData: CallInviteData = {
businessID: callType === 'video' ? 'video_call' : 'voice_call',
actionType: 1, // 邀请
callType: callType,
roomId: generateRoomId(),
inviter: getCurrentUserID(),
invitee: inviteeID,
timestamp: Date.now()
}
const payload = {
data: JSON.stringify(callData),
description: `${callType === 'video' ? '视频' : '语音'}通话邀请`,
extension: 'call_v1.0'
}
try {
const message = timService.createCustomMessage(
inviteeID,
payload,
TIM.TYPES.CONV_C2C,
TIM.TYPES.MSG_PRIORITY_HIGH
)
// 同时发送信令邀请
const signalResult = await timService.invite(
inviteeID,
JSON.stringify(callData),
30 // 30秒超时
)
// 将信令 ID 添加到消息中
message.signalInviteID = signalResult.inviteID
const result = await timService.sendMessage(message)
console.log('📞 通话邀请发送成功')
return {
message: result,
signalInviteID: signalResult.inviteID
}
} catch (error) {
console.error('❌ 通话邀请发送失败:', error)
throw error
}
}
// 4. 创建充值请求消息
async function sendRechargeRequest(receiverID: string, amount: number, reason?: string) {
const rechargeData: RechargeRequestData = {
businessID: 'ask_recharge',
amount: amount,
currency: 'USD', // 或从配置中获取
reason: reason,
timestamp: Date.now()
}
const payload = {
data: JSON.stringify(rechargeData),
description: `充值请求: $${amount}`,
extension: 'recharge_v1.0'
}
const message = timService.createCustomMessage(
receiverID,
payload,
TIM.TYPES.CONV_C2C
)
return await timService.sendMessage(message)
}
// 5. 创建官方系统消息
async function sendOfficialMessage(receiverID: string, messageData: any) {
const officialData: OfficialMessageData = {
businessID: 'official_message',
messageType: messageData.type,
title: messageData.title,
content: messageData.content,
priority: messageData.priority || 'normal',
timestamp: Date.now()
}
const payload = {
data: JSON.stringify(officialData),
description: messageData.title,
extension: 'official_v1.0'
}
const message = timService.createCustomMessage(
receiverID,
payload,
TIM.TYPES.CONV_C2C,
messageData.priority === 'high' ? TIM.TYPES.MSG_PRIORITY_HIGH : TIM.TYPES.MSG_PRIORITY_NORMAL
)
return await timService.sendMessage(message)
}
// 辅助函数
function generateRoomId(): string {
return `room_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
function getCurrentUserID(): string {
const imStore = useIMStore()
return imStore.userInfo?.userID || ''
}
async function generateMapThumbnail(lat: number, lng: number): Promise<string> {
// 调用地图服务生成缩略图
const mapService = 'https://maps.googleapis.com/maps/api/staticmap'
const params = new URLSearchParams({
center: `${lat},${lng}`,
zoom: '15',
size: '200x200',
markers: `${lat},${lng}`,
key: 'YOUR_GOOGLE_MAPS_API_KEY'
})
return `${mapService}?${params.toString()}`
}
function triggerGiftAnimation(giftData: GiftMessageData) {
// 触发礼物动画效果
const event = new CustomEvent('giftAnimation', {
detail: giftData
})
document.dispatchEvent(event)
}
自定义消息处理器:
typescript
// 自定义消息处理器
class CustomMessageHandler {
private handlers: Map<string, (data: any) => void> = new Map()
// 注册处理器
registerHandler(businessID: string, handler: (data: any) => void) {
this.handlers.set(businessID, handler)
}
// 处理接收到的自定义消息
handleCustomMessage(message: any) {
try {
const payload = JSON.parse(message.payload.data)
const handler = this.handlers.get(payload.businessID)
if (handler) {
handler(payload)
} else {
console.warn('未找到对应的消息处理器:', payload.businessID)
}
} catch (error) {
console.error('处理自定义消息失败:', error)
}
}
}
// 全局消息处理器实例
const messageHandler = new CustomMessageHandler()
// 注册各种消息处理器
messageHandler.registerHandler('gift', (data: GiftMessageData) => {
console.log('🎁 收到礼物:', data.giftName)
showGiftNotification(data)
updateGiftStatistics(data)
})
messageHandler.registerHandler('location', (data: LocationMessageData) => {
console.log('📍 收到位置分享:', data.address)
showLocationOnMap(data)
})
messageHandler.registerHandler('video_call', (data: CallInviteData) => {
console.log('📞 收到视频通话邀请')
showCallInviteDialog(data)
})
messageHandler.registerHandler('ask_recharge', (data: RechargeRequestData) => {
console.log('💰 收到充值请求:', data.amount)
showRechargeDialog(data)
})
// 在消息接收处理中使用
function handleReceivedMessage(message: any) {
if (message.type === TIM.TYPES.MSG_CUSTOM) {
messageHandler.handleCustomMessage(message)
}
}