转发消息
IMKit 默认没有实现对单条、多条消息的转发以及合并转发,下面提供了可以实现单条或多条消息的转发的代码示例进行参考。



局限
- 并非所有消息类型均支持合并转发。
- 支持的消息类型:文本、图片、图文、GIF、动态表情(
RC:StkMsg
)、名片、位置、小视频、文件、普通语音、高清语音、音视频通话(RC:VCSummary
)。 - 不支持的情况:未在支持列表中的消息类型,例如引用消息,以及未发送成功的消息等特殊情况不支持转发。自定义消息均不支持合并转发。
- 支持的消息类型:文本、图片、图文、GIF、动态表情(
- 合并转发支持合并不能超过 100 条消息。
单条转发
可以增加消息气泡的长按事件来实现转发。在 onClick
中发消息使用 示例代码 的 sendForwardMessage
方法。
let msgForwardLongClickAction :ItemLongClickAction<Message> = {
obtainTitle: (context: Context, data: Message): string | Resource => {
return "转发消息"
},
onClick: (context: Context, data: Message): void => {
// 转发消息
},
onFilter: (data: Message): boolean => {
// 是否显示该长按事件?true 显示;false 不显示
// 开发者可以根据 Message 对象的会话类型或者消息类型决定是否显示
return true;
},
// 自定义的消息长按事件 Id,相同的 Id 的长按事件只会增加一次
actionId: 'CustomMessageActionId'
}
RongIM.getInstance().conversationService().addMessageItemLongClickAction(msgForwardLongClickAction);

合并转发(1.4.3 支持)
SDK 会将选中的消息合并为一条合并转发消息,包含消息内容对象 CombineMessage
(类型标识:RC:CombineMsg
)。合并转发的消息默认折叠显示,可点击展开。
预览页面事件监听
您可以通过 addCombineMessageEventListener
方法自定义合并转发预览界面的事件监听:
示例代码
export interface CombineMessageEventListener {
/**
* 合并转发消息预览页面的位置消息点击事件回调
* @param latitude 纬度, double 类型
* @param longitude 经度, double 类型
* @param locationName 地理位置的名称
*/
onLocationMessageClick?: (latitude: number, longitude: number, locationName: string) => void;
}
let combineMessageEventListener: CombineMessageEventListener = {
onLocationMessageClick: (latitude: number, longitude: number, locationName: string) => {
// 地图消息跳转地图页面查看位置
}
}
// 添加事件监听
RongIM.getInstance().messageService().addCombineMessageEventListener(combineMessageEventListener)
// 移除事件监听
RongIM.getInstance().messageService().removeCombineMessageEventListener(combineMessageEventListener)
预览页面样式设置
您可以通过 ConversationConfig
的 setCombineHtmlStyle
方法配置预览页面的样式:
let config = RongIM.getInstance().conversationService().getConversationConfig()
config.setCombineHtmlStyle("样式style")
RongIM.getInstance().conversationService().setConversationConfig(config)
合并转发兼容不支持的类型消息
构建合并转发消息
构建合并转发接口新增 function
类型参数 unsupportedMessageHandler?: (message:Message) => Promise<string>
。仅当SDK解析到不支持的类型消息时,会回调此 function
,message
是不支持的消息。
这样由开发者来处理合并转发不支持的类型消息,返回消息对应 HTML body 内容,SDK 会把返回的内容插入到合并转发 HTML 中。
/**
* 构建合并转发消息
*
* @param forwardMessages 用来构建合并转发消息的消息数组
* @param unsupportedMessageHandler 用于开发者处理合并转发不支持类型的消息转换为 HTML 内容的逻辑。
* @returns 返回 CombineMessage,如果返回 undefined 则代表构建失败。
*/
obtainCombineMessage(forwardMessages: Message[], unsupportedMessageHandler?: (message:Message) => Promise<string>): Promise<IAsyncResult<CombineMessage>>;
参数说明
参数名 | 类型 | 说明 |
---|---|---|
forwardMessages | Message[] | 用来构建合并转发消息的消息数组 |
unsupportedMessageHandler | (message:Message) => Promise<string> | 用于开发者处理合并转发不支持类型的消息转换为 HTML 内容的逻辑。 1. 仅当 SDK 转换 forwardMessages 遇到不支持的消息类型时回调此 function,message 是不支持的消息。如开发者准备渲染该消息,则需要返回 body 内容。2. SDK 不会校验 function 返回的 body 内容,如内容异常会导致合并转发页面加载失败,需开发者保证内容有效。如返回空字符串则不会处理。 |
拦截Html页面的JS事件
接口说明
export interface CombineMessageEventListener {
// ...省略无关代码
/**
* 合并转发WebView的JS回调原生的拦截器。此拦截接口优先级高于 onLocationMessageClick。
*
* @params jsJson 合并转发Html通过JS透传过来的Json数据
* @return false 代表SDK继续执行JS事件,true 代表SDK不需再执行JS事件。
* @since 1.7.0
*/
onJSCallNativeInterceptor?: (jsJson: string) => Promise<boolean>;
}
SDK 返回的 jsJson 数据示例
文件
type = "RC:FileMsg"
fileName = "文件.pdf"
fileSize = "123456"
fileType = "pdf"
fileUrl = "文件地址"
地图
type = "RC:LBSMsg"
latitude = "纬度"
locationName = "地理位置"
longitude = "经度"
合并转发
type = "RC:CombineMsg"
fileUrl = "文件下载地址"
title = "标题"
手机号
type = "phone"
phoneNum = "13888888888"
超链接
type = "link"
link = "超链接"
图片
type = "RC:ImgMsg"
fileUrl = "图片地址"
imgUrl = "缩略图base64"
视频
type = "RC:SightMsg"
duration = "5"
fileUrl = "视频地址"
imageBase64 = "缩略图base64"
Gif
type = "RC:GIFMsg"
fileUrl = "Gif地址"
自定义html内容说明
定义 CUSTOM:MSG
类型消息对应的 html body 内容,支持使用自定义样式,支持点击事件。
- 自定义样式可以通过
setCombineHtmlStyle
来设置。 - 必须设置
onClick='show()'
来保证 SDK 可以接收 JS 点击事件,例:onClick='show({extra:"自定义透传参数",title:"标题",type:"CUSTOM:MSG"})'
。 - 设置合并转发事件接口,可以拦截JS点击事件,参照
CombineMessageEventListener#onJSCallNativeInterceptor
。 - 内置标签如:
{%portrait%}
、{%showUser%}
、{%userName%}
、{%sendTime%}
,与消息类型无关,由 SDK 进行处理。
代码示例
<!-- 1,点击事件:必须统一使用show()来进行鸿蒙原生JS回调,内容是Json格式 -->
<div class='rong-message {%showUser%}' onClick='show({extra:"自定义透传参数",title:"标题",type:"CUSTOM:MSG"})'>
<div class='rong-message-user rong-none-user-img'><img src='{%portrait%}' class='rong-message-user-bg rong-message-user-portrait' /></div>
<div class='rongcloud-message-body'>
<!-- 2,修改name修改为消息的objectName -->
<div name='CUSTOM:MSG'>
<div class='rongcloud-message-user-name'><span name='userName'>{%userName%}</span><span class='rong-message-time' name='sendTime'>{%sendTime%}</span></div>
<!-- 3,摆放 html body 标签 -->
<!-- 这里rongcloud-message-text 是内置的样式 -->
<div class='rongcloud-message-text'>
<pre class='rongcloud-message-entry'>{%customMsgField1_SDKStyle%}</pre>
</div>
<!-- 这里 custom-msg-style1、custom-msg-style2 是自定义的样式 -->
<div class='custom-msg-style1'>
<pre class='rongcloud-message-entry'>{%customMsgField2_CustomStyle%}</pre>
</div>
<div class='custom-msg-style2'>
<pre class='rongcloud-message-entry'>{%customMsgField3_CustomStyle%}</pre>
</div>
</div>
</div>
</div>
完整示例
- 定义 CUSTOM:MSG 类型消息的 HTML 内容
let customMsgHtmlData:string =
"<div class='rong-message {%showUser%}' onClick='show({extra:\"自定义透传参数\",title:\"标题\",type:\"CUSTOM:MSG\"})'>\n" +
" <div class='rong-message-user rong-none-user-img'><img src='{%portrait%}' class='rong-message-user-bg rong-message-user-portrait' /></div>\n" +
" <div class='rongcloud-message-body'>\n" +
" <!-- 2,修改name修改为消息的objectName -->\n" +
" <div name='CUSTOM:MSG'>\n" +
" <div class='rongcloud-message-user-name'><span name='userName'>{%userName%}</span><span class='rong-message-time' name='sendTime'>{%sendTime%}</span></div>\n" +
" <!-- 3,摆放 html body 标签 -->\n" +
" <!-- 这里rongcloud-message-text 是内置的样式 -->\n" +
" <div class='rongcloud-message-text'>\n" +
" <pre class='rongcloud-message-entry'>{%customMsgField1_SDKStyle%}</pre>\n" +
" </div>\n" +
" <!-- 这里 custom-msg-style1、custom-msg-style2 是自定义的样式 -->\n" +
" <div class='custom-msg-style1'>\n" +
" <pre class='rongcloud-message-entry'>{%customMsgField2_CustomStyle%}</pre>\n" +
" </div>\n" +
" <div class='custom-msg-style2'>\n" +
" <pre class='rongcloud-message-entry'>{%customMsgField3_CustomStyle%}</pre>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
"</div>";
- 定义 HTML 内容中用到的样式
import { RongIM } from "@rongcloud/imkit"
let config = RongIM.getInstance().conversationService().getConversationConfig()
let style: string = ".custom-msg-style1 {\n" +
" font-size: 14px;\n" +
" color: #FF0000;\n" +
" margin: 0;\n" +
" padding: 0;\n" +
" }\n" +
" .custom-msg-style2 {\n" +
" font-size: 16px;\n" +
" color: #CC0066;\n" +
" margin: 0;\n" +
" padding: 0;\n" +
" }"
config.setCombineHtmlStyle(style)
RongIM.getInstance().conversationService().setConversationConfig(config)
- 设置合并转发事件接口,实现拦截JS事件方法
import { CombineMessageEventListener, RongIM } from "@rongcloud/imkit";
let combineMessageEventListener: CombineMessageEventListener = {
onJSCallNativeInterceptor: (jsData: string): Promise<boolean> => {
return new Promise((resolve) => {
let jsonObject: object = JSON.parse(jsData);
// 开发者根据 jsonObject 里的值来判断开发者是否需要处理,以及是否需要SDK处理。
if (jsonObject["CUSTOM:MSG"]) {
// 开发者处理跳转逻辑,则返回true,由SDK处理跳转逻辑则返回false。
resolve(true);
} else {
resolve(false);
}
});
}
}
RongIM.getInstance().messageService().addCombineMessageEventListener(combineMessageEventListener)
- 构建转发消息,设置转换Html的function来返回Html内容
import { Message, RongIM } from "@rongcloud/imkit"
// 参照上述 customMsgHtmlData 示例
let customMsgHtmlData: string = ""
// 待转发消息
let forwardMessage: Message[] = []
let result = await RongIM.getInstance().messageService().obtainCombineMessage(forwardMessage,
(message: Message) => {
return new Promise<string>((resolve) => {
if (message.objectName === "CUSTOM:MSG") {
// 示例replaceContent是从Message中取到的字段,准备替换到html模版中。
let field1 = "SDK样式+自定义消息字段1"
let field2 = "自定义样式+自定义消息字段2"
let field3 = "自定义样式+自定义消息字段3"
// 如果有多个内容需要替换,则逐个replace替换.
let html = customMsgHtmlData.replaceAll("{%customMsgField1_SDKStyle%}", field1)
.replaceAll("{%customMsgField2_CustomStyle%}", field2)
.replaceAll("{%customMsgField3_CustomStyle%}", field3);
resolve(html)
}else {
// 不需要解析的类型,返回空字符串
resolve("")
}
})
}
)
添加转发按钮
IMKit 提供了 addMessageMoreAction
方法,可以增加长按消息点击“更多”之后,展示聊天页面底部的按钮 示例代码。 需要在 routeSelectPage
中编写跳转到 App 侧开发选人页面。
逐条转发:多条转发消息使用 示例代码 的 sendForwardMessages
方法。
合并转发:合并转发消息使用 示例代码 的 sendForwardCombineMessage
方法。
forwardDialog: CustomDialogController | undefined
forwardMessageArray: Message[] = []
async aboutToAppear(): Promise<void> {
this.addMessageMoreItems()
}
private addMessageMoreItems() {
this.forwardDialog = new CustomDialogController({
builder: CustomListDialog({
itemTextAlign: TextAlign.Center,
viewAllWidth: '95%',
list: [$r("app.string.rc_stepwise_forwarding"), $r("app.string.rc_combine_forwarding"), $r("app.string.rc_cancel")],
onItemClick: (index: number) => {
// 关闭弹窗
this.forwardDialog?.close();
if (index === 2) {
return
}
// 跳转到 App 侧开发选人页面
let forWardType = index === 0 ? ForWardType.Step : ForWardType.Combine
let params: ForWardMessageParam = { messages: this.forwardMessageArray, forWardType: forWardType };
this.routeSelectPage(params)
}
}),
customStyle: true,
offset: { dx: 0, dy: -30 },
alignment: DialogAlignment.Bottom
})
RongIM.getInstance().conversationService().addMessageMoreAction({
actionId: 'message_more_forward',
icon: $r("app.media.rc_message_more_forward"),
onClick: (context: Context, messages: Message[]): boolean => {
this.forwardMessageArray = messages
this.forwardDialog?.open()
return true
},
onFilter: (messages: Message[]): boolean => {
return true;
},
location: 100
})
}
private routeSelectPage(params: ForWardMessageParam) {
// 跳转逻辑
}
export interface ForWardMessageParam {
messages: Message[],
forWardType?: ForWardType
}
export enum ForWardType {
/**
* 逐条转发
*/
Step = 1,
/**
* 合并转发
*/
Combine = 2,
}
转发消息示例代码
import {
ConversationIdentifier,
ConversationType,
FileMessage,
FileMessageObjectName,
GIFMessage,
GIFMessageObjectName,
ImageMessage,
ImageMessageObjectName,
HQVoiceMessageObjectName,
Message,
ObjectChecker,
RongIM,
TextMessage,
SightMessage,
TextMessageObjectName,
SightMessageObjectName,
LocationMessageObjectName,
LocationMessage,
HQVoiceMessage,
RichContentMessageObjectName,
RichContentMessage,
ReferenceMessageObjectName,
ReferenceMessage,
MessageContent
} from '@rongcloud/imkit';
// 转发多条消息
private async sendForwardMessages(forwardMessage: Message[], conversation: Conversation) {
for (let index = 0; index < forwardMessage.length; index++) {
this.sendForwardMessage(forwardMessage[index], conversation);
await new Promise<void>(resolve => setTimeout(resolve, 300));
}
}
// 转发单条消息
private async sendForwardMessage(forwardMessage: Message, conversation: Conversation): Promise<void> {
return new Promise(async () => {
let conversationIdentifier = ConversationIdentifier.createWithConversation(conversation)
let msgContent = this.getMessageContent(forwardMessage.content, forwardMessage.objectName);
if (!msgContent) {
return
}
let msg = new Message(conversationIdentifier, msgContent);
await RongIM.getInstance().messageService().sendMessage(msg)
})
}
// 合并转发消息
public async sendForwardCombineMessage(forwardMessage: Message[], conversation: Conversation): Promise<void> {
return new Promise(async () => {
let result = await RongIM.getInstance().messageService().obtainCombineMessage(forwardMessage)
if (result.code !== EngineError.Success) {
return
}
let combineMsg = result.data as CombineMessage
let msg = new Message(ConversationIdentifier.createWithConversation(conversation), combineMsg)
await RongIM.getInstance().messageService().sendMediaMessage(msg)
});
}
private getMessageContent(messageContent: MessageContent, objName: string): MessageContent | undefined {
if (objName === TextMessageObjectName) {
let oldTextMsg = messageContent as TextMessage
let textMsg = new TextMessage();
textMsg.content = oldTextMsg.content;
return textMsg
} else if (objName === ImageMessageObjectName) {
let imageMsg = messageContent as ImageMessage
let imageMessage = new ImageMessage();
imageMessage.isFull = true;
imageMessage.name = imageMsg.name;
imageMessage.remoteUrl = imageMsg.remoteUrl;
imageMessage.thumbnailBase64 = imageMsg.thumbnailBase64;
return imageMessage
} else if (objName === FileMessageObjectName) {
let fileMsg = messageContent as FileMessage;
let fileMessage = new FileMessage();
fileMessage.name = fileMsg.name;
fileMessage.remoteUrl = fileMsg.remoteUrl;
fileMessage.size = fileMsg.size;
fileMessage.type = fileMsg.type;
return fileMessage
} else if (objName === GIFMessageObjectName) {
let imageMsg = messageContent as GIFMessage
let imageMessage = new GIFMessage();
imageMessage.name = imageMsg.name;
imageMessage.remoteUrl = imageMsg.remoteUrl;
return imageMessage
} else if (objName === SightMessageObjectName) {
let sightMsg = messageContent as SightMessage
let sightMessage = new SightMessage();
sightMessage.base64 = sightMsg.base64;
sightMessage.duration = sightMsg.duration;
sightMessage.size = sightMsg.size;
sightMessage.name = sightMsg.name;
sightMessage.remoteUrl = sightMsg.remoteUrl;
return sightMessage
} else if (objName === RichContentMessageObjectName) {
let richContentMsg = messageContent as RichContentMessage
let richContentMessage = new RichContentMessage();
richContentMessage.title = richContentMsg.title
richContentMessage.content = richContentMsg.content
richContentMessage.imgUrl = richContentMsg.imgUrl
richContentMessage.url = richContentMsg.url
return richContentMessage
} else if (objName === LocationMessageObjectName) {
let locationMsg = messageContent as LocationMessage
let locationMessage = new LocationMessage();
locationMessage.latitude = locationMsg.latitude;
locationMessage.longitude = locationMsg.longitude;
locationMessage.poi = locationMsg.poi;
locationMessage.thumbnailBase64 = locationMsg.thumbnailBase64;
locationMessage.type = locationMsg.type;
return locationMessage
} else if (objName === HQVoiceMessageObjectName) {
let hqVoiceMsg = messageContent as HQVoiceMessage
let hqVoiceMessage = new HQVoiceMessage();
hqVoiceMessage.name = hqVoiceMsg.name;
hqVoiceMessage.remoteUrl = hqVoiceMsg.remoteUrl;
hqVoiceMessage.duration = hqVoiceMsg.duration;
return hqVoiceMessage
} else if (objName === CombineMessageObjectName) {
let combineMsg = messageContent as CombineMessage
let combineMessage = new CombineMessage();
combineMessage.name = combineMsg.name;
combineMessage.title = combineMsg.title;
combineMessage.conversationType = combineMsg.conversationType;
combineMessage.nameList = combineMsg.nameList
combineMessage.summaryList = combineMsg.summaryList
combineMessage.remoteUrl = combineMsg.remoteUrl
return combineMessage
}
return undefined
}