/** * 腾讯 IM SDK 封装 * 负责初始化、登录、收发消息 */ import TencentCloudChat from '@tencentcloud/chat' import request from './request' let chat = null let isReady = false let onMessageCallback = null /** * 获取 IM 实例(单例) */ export function getChatInstance() { return chat } /** * 初始化并登录 IM * @returns {Promise<{sdkAppId, userId}>} */ export async function initIM() { // 从后端获取 UserSig const res = await request({ url: '/api/im/usersig' }) const { sdkAppId, userId, userSig } = res // 创建 SDK 实例 if (!chat) { chat = TencentCloudChat.create({ SDKAppID: sdkAppId }) chat.setLogLevel(1) // Release 级别日志 // 监听 SDK 就绪 chat.on(TencentCloudChat.EVENT.SDK_READY, () => { console.log('[IM] SDK 就绪') isReady = true }) // 监听新消息 chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, (event) => { const msgList = event.data if (onMessageCallback) { onMessageCallback(msgList) } }) // 监听被踢下线 chat.on(TencentCloudChat.EVENT.KICKED_OUT, () => { console.log('[IM] 被踢下线') isReady = false }) } // 登录 await chat.login({ userID: userId, userSig }) console.log('[IM] 登录成功:', userId) return { sdkAppId, userId } } /** * 登出 IM */ export async function logoutIM() { if (chat) { await chat.logout() isReady = false console.log('[IM] 已登出') } } /** * 设置新消息回调 * @param {Function} callback - 接收消息列表的回调 */ export function onNewMessage(callback) { onMessageCallback = callback } /** * 移除新消息回调 */ export function offNewMessage() { onMessageCallback = null } /** * 获取会话 ID(C2C 单聊) * @param {string} targetUserId - 对方用户 ID(如 user_123) */ export function getConversationId(targetUserId) { return `C2C${targetUserId}` } /** * 获取会话列表(用于消息页展示聊天记录) * @returns {Promise} 会话列表 */ export async function getConversationList() { if (!chat || !isReady) return [] try { const res = await chat.getConversationList() const list = res.data.conversationList || [] // 只返回 C2C 单聊会话 return list .filter(c => c.type === TencentCloudChat.TYPES.CONV_C2C) .map(c => { const lastMsg = c.lastMessage let lastMessageText = '' if (lastMsg) { if (lastMsg.type === TencentCloudChat.TYPES.MSG_TEXT) { lastMessageText = lastMsg.payload?.text || '' } else if (lastMsg.type === TencentCloudChat.TYPES.MSG_IMAGE) { lastMessageText = '[图片]' } else if (lastMsg.type === TencentCloudChat.TYPES.MSG_CUSTOM) { lastMessageText = lastMsg.payload?.description || '[自定义消息]' } else { lastMessageText = '[消息]' } } return { conversationID: c.conversationID, targetUserId: c.userProfile?.userID || c.conversationID.replace('C2C', ''), nickname: c.userProfile?.nick || c.conversationID.replace('C2C', ''), avatarUrl: c.userProfile?.avatar || '', lastMessage: lastMessageText, lastMessageTime: lastMsg ? lastMsg.lastTime * 1000 : 0, unreadCount: c.unreadCount || 0 } }) .sort((a, b) => b.lastMessageTime - a.lastMessageTime) } catch (e) { console.error('[IM] 获取会话列表失败:', e) return [] } } /** * 拉取历史消息 * @param {string} targetUserId - 对方用户 ID * @param {string} [nextReqMessageID] - 分页标记 * @returns {Promise<{messageList, isCompleted, nextReqMessageID}>} */ export async function getMessageList(targetUserId, nextReqMessageID) { const conversationID = getConversationId(targetUserId) const res = await chat.getMessageList({ conversationID, nextReqMessageID, count: 20 }) return { messageList: res.data.messageList || [], isCompleted: res.data.isCompleted, nextReqMessageID: res.data.nextReqMessageID } } /** * 发送文本消息 * @param {string} targetUserId - 对方用户 ID * @param {string} text - 文本内容 */ export async function sendTextMessage(targetUserId, text) { const message = chat.createTextMessage({ to: targetUserId, conversationType: TencentCloudChat.TYPES.CONV_C2C, payload: { text } }) const res = await chat.sendMessage(message) return res.data.message } /** * 发送图片消息 * @param {string} targetUserId - 对方用户 ID * @param {string} filePath - 本地图片路径 */ export async function sendImageMessage(targetUserId, filePath) { const message = chat.createImageMessage({ to: targetUserId, conversationType: TencentCloudChat.TYPES.CONV_C2C, payload: { file: { tempFilePaths: [filePath] } } }) const res = await chat.sendMessage(message) return res.data.message } /** * 发送自定义消息(改价申请等) * @param {string} targetUserId - 对方用户 ID * @param {Object} customData - 自定义数据 */ export async function sendCustomMessage(targetUserId, customData) { const message = chat.createCustomMessage({ to: targetUserId, conversationType: TencentCloudChat.TYPES.CONV_C2C, payload: { data: JSON.stringify(customData), description: customData.description || '', extension: customData.extension || '' } }) const res = await chat.sendMessage(message) return res.data.message } /** * 将 IM 消息转换为聊天页展示格式 * @param {Object} msg - IM SDK 消息对象 * @param {string} currentImUserId - 当前用户 IM ID * @returns {Object} 聊天页消息格式 */ export function formatIMMessage(msg, currentImUserId) { const isSelf = msg.from === currentImUserId const avatar = msg.avatar || '/static/logo.png' // 文本消息 if (msg.type === TencentCloudChat.TYPES.MSG_TEXT) { return { id: msg.ID, type: 'text', content: msg.payload.text, isSelf, avatar, time: msg.time * 1000 } } // 图片消息 if (msg.type === TencentCloudChat.TYPES.MSG_IMAGE) { const imageInfo = msg.payload.imageInfoArray?.[1] || msg.payload.imageInfoArray?.[0] return { id: msg.ID, type: 'image', content: imageInfo?.url || '', isSelf, avatar, time: msg.time * 1000 } } // 自定义消息(改价等) if (msg.type === TencentCloudChat.TYPES.MSG_CUSTOM) { try { const data = JSON.parse(msg.payload.data) if (data.bizType === 'price-change') { return { id: msg.ID, type: 'price-change', isSelf, priceChangeId: data.priceChangeId, changeTypeLabel: data.changeTypeLabel, originalPrice: data.originalPrice, newPrice: data.newPrice, difference: data.difference, status: data.status, time: msg.time * 1000 } } } catch (e) { // 解析失败当普通文本处理 } return { id: msg.ID, type: 'text', content: msg.payload.description || '[自定义消息]', isSelf, avatar, time: msg.time * 1000 } } // 其他类型 return { id: msg.ID, type: 'text', content: '[暂不支持的消息类型]', isSelf, avatar, time: msg.time * 1000 } }