campus-errand/miniapp/utils/im.js
18631081161 c543ebaf8b
All checks were successful
continuous-integration/drone/push Build is passing
聊天
2026-03-28 17:16:01 +08:00

324 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 腾讯 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)
// 同步用户资料(昵称+头像)到 IM
try {
const userInfo = JSON.parse(uni.getStorageSync('userInfo') || '{}')
if (userInfo.nickname || userInfo.avatarUrl) {
const profileData = {}
if (userInfo.nickname) profileData.nick = userInfo.nickname
if (userInfo.avatarUrl) profileData.avatar = userInfo.avatarUrl
await chat.updateMyProfile(profileData)
console.log('[IM] 用户资料已同步')
}
} catch (e) {
console.warn('[IM] 同步用户资料失败:', e)
}
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
}
/**
* 获取会话 IDC2C 单聊)
* @param {string} targetUserId - 对方用户 ID如 user_123
*/
export function getConversationId(targetUserId) {
return `C2C${targetUserId}`
}
/**
* 获取会话列表(用于消息页展示聊天记录)
* @returns {Promise<Array>} 会话列表
*/
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 - 文本内容
* @param {string|number} [orderId] - 关联订单ID
*/
export async function sendTextMessage(targetUserId, text, orderId) {
const message = chat.createTextMessage({
to: targetUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: { text }
})
if (orderId) message.cloudCustomData = JSON.stringify({ orderId: String(orderId) })
const res = await chat.sendMessage(message)
return res.data.message
}
/**
* 发送图片消息
* @param {string} targetUserId - 对方用户 ID
* @param {string} filePath - 本地图片路径
* @param {string|number} [orderId] - 关联订单ID
*/
export async function sendImageMessage(targetUserId, filePath, orderId) {
const message = chat.createImageMessage({
to: targetUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: { file: { tempFilePaths: [filePath] } }
})
if (orderId) message.cloudCustomData = JSON.stringify({ orderId: String(orderId) })
const res = await chat.sendMessage(message)
return res.data.message
}
/**
* 发送自定义消息(改价申请等)
* @param {string} targetUserId - 对方用户 ID
* @param {Object} customData - 自定义数据
* @param {string|number} [orderId] - 关联订单ID
*/
export async function sendCustomMessage(targetUserId, customData, orderId) {
const message = chat.createCustomMessage({
to: targetUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify(customData),
description: customData.description || '',
extension: customData.extension || ''
}
})
if (orderId) message.cloudCustomData = JSON.stringify({ orderId: String(orderId) })
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'
// 提取消息关联的订单ID
let msgOrderId = null
if (msg.cloudCustomData) {
try {
const cd = JSON.parse(msg.cloudCustomData)
msgOrderId = cd.orderId || null
} catch (e) {}
}
// 文本消息
if (msg.type === TencentCloudChat.TYPES.MSG_TEXT) {
return {
id: msg.ID,
type: 'text',
content: msg.payload.text,
isSelf,
avatar,
time: msg.time * 1000,
orderId: msgOrderId
}
}
// 图片消息
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,
orderId: msgOrderId
}
}
// 自定义消息(改价、订单状态等)
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,
orderId: msgOrderId
}
}
if (data.bizType === 'order-status') {
return {
id: msg.ID,
type: 'system',
content: data.description || '[订单状态变更]',
time: msg.time * 1000,
orderId: msgOrderId
}
}
} catch (e) {}
return {
id: msg.ID,
type: 'text',
content: msg.payload.description || '[自定义消息]',
isSelf,
avatar,
time: msg.time * 1000,
orderId: msgOrderId
}
}
// 其他类型
return {
id: msg.ID,
type: 'text',
content: '[暂不支持的消息类型]',
isSelf,
avatar,
time: msg.time * 1000,
orderId: msgOrderId
}
}