消息与卡片
消息发送
本节说明文本、图片、视频、文件等消息的创建和发送能力。
创建消息和发送消息
SDK 提供了创建消息和发送消息的方法。App 可以通过 UnicornMessageBuilder 创建不同类型的 IMessage,然后通过 MessageService.sendMessage() 发送消息。
创建消息
SDK 中创建消息的方法都在 UnicornMessageBuilder 类中,该类提供到了创建不同类型消息的方法。
文本消息
public static final IMMessage buildTextMessage(String sessionId, String text)参数说明
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| sessionId | String | 聊天对象ID | 标准版:UnicornMessageBuilder.getSessionId()平台版:商户ID |
| text | String | 文本消息内容 | 需要发送的文本消息 |
图片消息
public static final IMMessage buildImageMessage(String sessionId, File file, String displayName)参数说明
| 字段 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| sessionId | String | 聊天对象ID | 标准版:UnicornMessageBuilder.getSessionId()平台版:商户ID |
| file | File | 图片文件 | 需要发送的图片文件 |
| displayName | String | 图片文件的显示名 | 文件的显示名称 |
视频消息
public static final IMMessage buildVideoMessage(File file, long duration, int width, int height, String displayName)
public static final IMMessage buildVideoMessage(String sessionId, File file, long duration, int width, int height, String displayName)参数说明
| 字段 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| sessionId | String | 聊天对象ID | 标准版:UnicornMessageBuilder.getSessionId()平台版:商户ID |
| file | File | 视频文件 | 需要发送的视频文件 |
| duration | long | 视频文件时长 | 可通过 MediaPlayer 获取 |
| width | int | 视频文件宽 | 可通过 MediaPlayer 获取 |
| height | int | 视频文件高 | 可通过 MediaPlayer 获取 |
| displayName | String | 视频文件名字 | 可通过 file.getName 获取 |
文件消息
public static IMMessage buildFileMessage(String sessionId, String filePath)
public static IMMessage buildFileMessage(String sessionId, Uri uri)参数说明
| 字段 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| sessionId | String | 聊天对象ID | 标准版:UnicornMessageBuilder.getSessionId()平台版:商户ID |
| filePath | String | 文件路径 | 需要发送的文件路径 |
| uri | Uri | 文件Uri | 需要发送的文件Uri |
代码示例
IMMessage message = UnicornMessageBuilder.buildTextMessage(UnicornMessageBuilder.getSessionId(), "文本消息");App 侧创建消息时,sessionId 表示聊天对象 ID,不是访客 userId。标准版通常使用 UnicornMessageBuilder.getSessionId();平台版需要传当前商家的 shopId。
// 标准版
String sessionId = UnicornMessageBuilder.getSessionId();
IMMessage message = UnicornMessageBuilder.buildTextMessage(sessionId, "文本消息");
MessageService.sendMessage(message);
// 平台版
String sessionId = shopId;
IMMessage platformMessage = UnicornMessageBuilder.buildTextMessage(sessionId, "文本消息");
MessageService.sendMessage(platformMessage);发送消息
SDK 中发送消息的方法的实现都在 MessageService 中,该类提供到了发送不同消息的方法。
普通消息
public static void sendMessage(IMMessage message)参数说明
| 字段 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| message | IMMessage | 需要发送的消息 | 创建消息的返回值 |
本地消息
把消息存储本地的方法,调用该方法之后,本条消息只能访客看到,客服看不到
public static void saveMessageToLocal(IMMessage message, boolean isNotify, boolean isSaveDB)参数说明
| 字段 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| message | IMMessage | 需要发送的消息 | 创建消息的返回值 |
| isNotify | boolean | 是否显示到消息流 | 客户根据场景选择 |
| isSaveDB | boolean | 是否将消息存储到本地数据库 | 客户根据场景选择 |
自定义本地消息
SDK 开放了自定义本地消息的功能,开发者可以通过 SDK 开放的 API 实现任何样式的本地消息。
具体实现涉及两块内容,一块是数据源(SDK中的MsgAttachment),一块是我们展示消息的 View (SDK 中的 UnicornMessageViewHolder),开发者可以通过实现 MsgAttachment 和 UnicornMessageViewHolder 来实现该功能,具体实现方案如下:
- 实现自己的 MsgAttachment ,数据源里面的数据开发者可以随意定义。
- 实现自己的 UnicornMessageViewHolder,该类中主要涉及三个方法,getViewHolderResid (返回 item 的 layout布局),inflateFindView(进行 item 中的 findViewById 操作),bindContentView(数据绑定操作)。
- 通过监听 SDK 的事件,当 eventType = 2 的时候,去注册我们的数据源解析器,因为我们发送出去的消息都是一个 String 类型的消息,所以开发者需要在解析器中把这个 String 类型的消息解析成自己的 MsgAttachment。
- 通过 MsgCustomizationRegistry.registerMessageViewHolder 方法,将数据源和 viewHolder 联系起来。
- 通过 UnicornMessageBuilder.buildCustomMessage(开发者实现的 MsgAttachment) 方法去构建一条消息,然后通过 MessageService.sendMessage 去发送消息。
具体实现基本就这 5 步,看一下每步的实现:
/**
* 第一步实现自己的 MsgAttachment
*/
public class DemoAttachment implements MsgAttachment{
public static final String TAG_STRING = "tag_string";
public static final String TAG_URL = "tag_url";
private String msgContent;
private String imgUrl;
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getMsgContent() {
return msgContent;
}
public void setMsgContent(String msgContent) {
this.msgContent = msgContent;
}
//这个方法必须重写,因为在存入数据库的时候需要用到
@Override
public String toJson(boolean send) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put(TAG_STRING,getMsgContent());
jsonObject.put(TAG_URL,getImgUrl());
} catch (JSONException e) {
//log
}
return jsonObject.toString();
}
@Override
public boolean countToUnread() {
return false;
}
@Override
public String getContent(Context context) {
return msgContent;
}
}
/**
* 第二步实现自己的 UnicornMessageViewHolder
*/
public class DemoCustomViewHolder extends UnicornMessageViewHolder{
private TextView tvViewHolderDemoText;
private TextView tvViewHolderDemoUrl;
@Override
public int getViewHolderResid() {
return R.layout.viewholder_demo_custom;
}
@Override
public void inflateFindView() {
tvViewHolderDemoText = findViewById(R.id.tv_view_holder_demo_text);
tvViewHolderDemoUrl = findViewById(R.id.tv_view_holder_demo_url);
}
@Override
public void bindContentView(IMMessage message, Context context) {
DemoAttachment attachment = (DemoAttachment) message.getAttachment();
tvViewHolderDemoText.setText(attachment.getMsgContent());
tvViewHolderDemoUrl.setText(attachment.getImgUrl());
progressBar.setVisibility(View.GONE);
}
@Override
protected boolean isMiddleItem() {
return true;
}
@Override
protected boolean showAvatar() {
return false;
}
}
/**
* 第三步请见上文所述的拦截 SDK 事件一节
*/
/**
* 第四步注册 Attachment 和 ViewHolder 的关系
* 该步可以在任何地方调用,但切记在发送自定义消息之前
*/
MsgCustomizationRegistry.registerMessageViewHolder(DemoAttachment.class, DemoCustomViewHolder.class);
/**
* 第五步创建消息并保存消息到本地
*/
IMMessage message = UnicornMessageBuilder.buildAppCustomMessage(attachment);
//具体参数请查看 MessageService 类的说明
MessageService.saveMessageToLocal(message, true, true);商品卡片
商品卡片使用 ProductDetail 描述内容,通过 ConsultSource.productDetail 在打开会话时携带,或通过 MessageService.sendProductMessage() 在会话中主动发送。
发送接口
public static void sendProductMessage(ProductDetail productDetail)
public static void sendProductMessage(String shopId, ProductDetail productDetail)参数说明
| 字段 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| productDetail | ProductDetail | 商品卡片对象 | 字段见下方 ProductDetail 配置 |
| shopId | String | 商户 ID | 标准版使用不带 shopId 的重载;平台版填写目标商户 ID |
ProductDetail 配置
ProductDetail 用于描述商品或订单消息的内容和发送行为。
ConsultSource 相关配置
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| productDetail | ProductDetail | 访客发起会话时携带的商品卡片 | 默认为 null,表示不携带 |
| isSendProductonRobot | boolean | 机器人状态是否发送商品卡片 | 默认为 false,表示不发送 |
| isSendProductionLeave | boolean | 留言状态是否发送商品或消息卡片 | 默认为 false,表示不发送 |
ProductDetail 字段
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| title | String | 商品标题 | 长度限制为 100 字符,超过自动截断 |
| desc | String | 商品详细描述信息 | 长度限制为 300 字符,超过自动截断 |
| picture | String | 缩略图 URL | 图片需允许客服端访问;长度限制为 1000 字符,超长不会自动截断,但会导致发送失败 |
| url | String | 商品详情页 URL | 长度限制为 1000 字符,超长不会自动截断,但会导致发送失败 |
| note | String | 商品备注信息,如价格、套餐等 | 长度限制为 100 字符,超过自动截断 |
| show | int | 是否在访客端显示商品卡片 | 默认为 0;0 表示仅客服可见,1 表示访客和客服均可见 |
| alwaysSend | boolean | 会话开始后是否仍可发送商品卡片 | 默认为 false,仅在连接客服时发送一次;设为 true 后可在会话中继续发送,相同商品链接不会重复发送 |
| openTemplate | boolean | 是否使用新版商品展示样式 | 默认为 false;开启后,发送的商品仅展示配置的图片 |
| sendByUser | boolean | 是否由访客手动发送 | 默认为 false;设为 true 后,商品下方展示发送按钮 |
| actionText | String | 手动发送按钮文案 | 默认文案为“发送链接” |
| actionTextColor | int | 手动发送按钮文字颜色 | Android ARGB 整数,例如 0xFFDB7093 |
| actionTextSize | float | 手动发送按钮文字大小 | 默认为 14 sp |
| isOpenReselect | boolean | 是否显示重新选择按钮 | 默认为 false,不显示 |
| reselectText | String | 重新选择按钮的文案 | 如果不设置,默认为"重新选择" |
| handlerTag | String | 当点击重新选择按钮的时候,会将此字段回传 | 默认为 null |
| ext | String | 扩展字段 | 默认为 null |
| cardType | int | 商品卡片的业务类型:0 为商品,1 为订单,2 为自定义商品类型 | 默认为 0;类型 2 仍属于 ProductDetail 商品卡片 |
| goodsCId | String | 商品类目 ID | 长度限制为 100 字符,填写后有助于机器人识别商品和后续业务 |
| goodsCName | String | 商品类目名称 | 长度限制为 10 个字符,如“生鲜食品” |
| goodsId | String | 商品 ID | 长度限制为 100 字符;卡片类型为商品时建议填写 |
| orderId | String | 订单 ID | 交易订单号;卡片类型为订单时建议填写 |
| intent | String | 意向内容 | 无默认值 |
| params | String | 一触即达流程参数 | 无默认值 |
| orderStageType | int | 订单状态分类 | 默认为 0 |
| optionId | String | 选项 ID | 无默认值 |
| tags | List<Tag> | 展示在客服端的一些可操作入口 | 默认为空,每个 tag 在客服端将展示为一个按钮 |
| tagString | String | 快捷入口列表 JSON 字符串 | 内容必须为 JSON 数组;与 tags 互斥,同时设置时以 tagString 为准 |
| productReslectOnclickListener | ProductReslectOnclickListener | 点击“重新选择”时的回调 | 默认不处理 |
| info | String | 卡片附加信息,不在卡片中展示 | 长度限制为 100 字符,超过自动截断 |
| agentId | String | 大模型 ID | 无默认值 |
| customLabelOrderId | String | 自定义标签的订单 ID | 无默认值 |
| customLabelOrderTime | String | 自定义标签的订单时间 | 无默认值 |
Tag
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| label | String | 操作入口的显示名称 | 无默认值 |
| url | String | 点击入口后的跳转地址 | 非必填;未设置 focusIframe 或未匹配到 iframe 时,在新窗口中打开 |
| focusIframe | String | 需要定位的客服工作台 iframe 标签名 | 非必填;以 iframe 名称匹配工作台右侧面板 |
| data | String | 打开 iframe 时传递给目标 iframe 的数据 | 非必填,数据格式不限 |
ProductReslectOnclickListener
| 接口回调函数 | 说明 |
|---|---|
| ProductReslectOnclickListener | 点击商品卡片中的“重新选择”按钮时触发 onClick 方法 |
代码示例
ProductDetail.Tag tag = new ProductDetail.Tag();
tag.setLabel("label");
tag.setUrl("http://qiyukf.com/");
ArrayList<ProductDetail.Tag> list = new ArrayList<>();
list.add(tag);
ProductDetail productDetail = new ProductDetail.Builder()
.setTitle("标题")
.setDesc("描述")
.setPicture("https://p9.itc.cn/images01/20230323/5ca2619b1743433ca9cd3e435edc455d.png")
.setUrl("http://qiyukf.com/")
.setNote("备注")
.setShow(1)
.setTags(list)
.setGoodsCId("商品类目ID")
.setGoodsCName("商品类目名称")
.setCardType(0)
.setGoodsId("aaa")
.build();打开会话时携带商品卡片
如果希望访客打开客服时自动把商品或订单信息带给客服,把 ProductDetail 设置到 ConsultSource.productDetail 后再打开会话。
ConsultSource source = new ConsultSource("goods_detail", "商品详情", null);
source.productDetail = productDetail;
Unicorn.openServiceActivity(context, "商品咨询", source);会话中主动发送商品卡片
如果会话已经打开,App 想在用户切换商品或订单后再次发送消息,可以调用 MessageService.sendProductMessage。标准版直接传 ProductDetail;平台版需要同时传当前商家的 shopId。
MessageService.sendProductMessage(productDetail);
// 平台版
MessageService.sendProductMessage(shopId, productDetail);商品卡片样式自定义
本功能用于自定义客服工作台 iframe 下发的商品卡片样式,接入流程一共分为 4 步。
- 在客服端需要发送如下格式的数据:
{
"productCustomField":"2342342342342342",
"isOpenCustomProduct":true,
"sendByUser":1,
"actionText":"fasonglianjiessz",
"actionTextColor":"#FF1493",
"template":"pictureLink",
"picture": "https://ysf.nosdn.127.net/32FD7DAACD42CE79DD03B1B82F690B7B?imageView&type=png",
"title": "[自营]Orange Juice",
"desc": "Relish the goodness of hand-picked oranges from the finest orchards. Foster a healthy lifestyle with the benefits of oranges. 100 percent orange juice with no added sugar for a healthy you.",
"price": "¥999",
"url": "http://10.242.118.244:8000/shop-detail.html",
"activity":"活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动",
"activityHref": "https://www.baidu.com",
"showCustomMsg":1
}之前发送 iframe 商品消息是没有 productCustomField 和 isOpenCustomProduct 这两个字段的,当 isOpenCustomProduct 为 true 的时候,商品消息的样式为自定义的,当为 false 的时候,使用 SDK 中默认的商品样式。所以如果想要使用自定义商品样式,发送数据中 isOpenCustomProduct 必须为 true。productCustomField 字段的意思是自定义商品消息的数据信息,用户可以把想要展示的自定义数据放到该字段中,当 SDK 收到该字段,会将该字段透传到 CustomProductParser 的 parseCustomProduct 方法中。
- 定义 Attachment 和 ViewHolder
一条消息由两部分组成,展示的数据和展示的样式,下面分两步讲解自定义商品消息体的实现。
因为每一种类型的消息数据格式都是不同的,自定义商品消息也不例外,所以需要定义自定义商品消息的 Attachment ,Attachment 中的字段由开发者自定义,想要展示什么数据就定义什么字段,代码如下:
//Attachment 一定要继承 UnicornAttachmentBase
public class DemoCustomProductAttachment extends UnicornAttachmentBase {
//想要展示的信息
private String customProductString;
// attach 字段可以通过 CustomProductParser 的 parseCustomProduct 的参数获取
public DemoCustomProductAttachment(String attach) {
super(attach);
}
public String getCustomProductString() {
return customProductString;
}
public void setCustomProductString(String customProductString) {
this.customProductString = customProductString;
}
}上面的代码只是简单的把 attach 直接拿过来使用,用户可以把 productCustomField 定义为一个 String 类型的 json,然后把 productCustomField 解析出来更多的字段使用。
自定义商品消息的消息体,也就是 ViewHolder,用户可以通过继承 UnicornMessageViewHolder 实现:
public class DemoCustomProductHolder extends UnicornMessageViewHolder {
private TextView tvDemoViewHolder;
//返回消息样式的layout id
@Override
public int getViewHolderResId() {
return R.layout.viewholder_product;
}
//初始化相关 view
@Override
public void inflateFindView() {
tvDemoViewHolder = findViewById(R.id.tv_demo_view_holder);
}
//进行数据绑定
@Override
public void bindContentView(IMMessage message, Context context) {
DemoCustomProductAttachment demoCustomProductAttachment = (DemoCustomProductAttachment) message.getAttachment();
tvDemoViewHolder.setText(demoCustomProductAttachment.getCustomProductString());
}
}上面的代码中只是比较简单的,用户可以自行扩展布局,用户也可以查看 UnicornMessageViewHolder 中的相关方法修改消息的样式。
- 定义解析器 Parser
因为当客服发送过来的消息是一个 String 类型的数据,所以需要把这个 String 解析成自定义商品消息对应的 Attachment,当用户需要解析自定义商品消息的时候 SDK 会去回调 CustomProductParser 的 parseCustomProduct 方法,用户可以在 YSFOptions 的 customProductParser 设置自己的 Parser,代码如下:
YSFOptions options = new YSFOptions();
options.customProductParser = DemoMsgParser.getInstance();上面的代码中指定了 DemoMsgParser 为自定义商品消息的解析器,代码如下:
public class DemoMsgParser implements CustomProductParser {
//单例对象
private static DemoMsgParser instance;
public static DemoMsgParser getInstance() {
if (instance == null) {
instance = new DemoMsgParser();
}
return instance;
}
private DemoMsgParser() {
}
//重写了自定义商品消息的解析方法,当有自定义商品消息的时候会回调该方法,用户需要把
//解析完成 Attachment return 给 SDK
@Override
public UnicornAttachmentBase parseCustomProduct(String attach) {
DemoCustomProductAttachment attachment = new DemoCustomProductAttachment(attach);
try {
JSONObject jsonObject = new JSONObject(attach);
attachment.setCustomProductString(jsonObject.getString("productCustomField"));
} catch (JSONException e) {
e.printStackTrace();
}
return attachment;
}
}上面的代码中实现了 CustomProductParser 接口,并实现了 parseCustomProduct 方法,该方法中我们我们通过解析 attach 字段,然后拿到了 productCustomField 字段(也就是客服发送过来的 productCustomField) 字段。客服端也可以把 productCustomField 里面塞一个 String 类型的 json 。这样 productCustomField 就可以解析出来更多的字段供展示使用。
- 注册 Attachment 和 ViewHolder 的对应关系
因为 SDK 需要把数据类型和 UI 对应起来,所以用户需要手动调用 MsgCustomizationRegistry.registerMessageViewHolder 方法告诉 SDK Attachment 和 ViewHolder 的对应关系(切记在 init 之后,打开客服界面之前调用),代码如下:
MsgCustomizationRegistry.registerMessageViewHolder(DemoCustomProductAttachment.class, DemoCustomProductHolder.class);上面展示了完成自定义商品消息的流程,如果有不理解的地方可以查看 Demo 中的源码和源码中的相关注释。
自定义卡片消息
自定义卡片消息由开发者通过 cardJson 组合内容,使用 CardDetail 在打开会话时携带,或使用 MessageService.sendCardMessage() 在会话中主动发送。cardJson 的根节点是 JSON 对象,各组件放在 cards 数组中;支持标题、富文本、副标题、图片、节点流程、商品、订单、按钮和跳转链接 9 种组件,一条消息可以组合多个不同或相同类型的组件。
会话中主动发送自定义卡片消息
public static void sendCardMessage(String cardJson)
public static void sendCardMessage(String cardJson, int sendProToRobot)
public static void sendCardMessage(String cardJson, int sendByUser, int sendProToRobot,
String actionText, String actionTextColor,
String intent, String params)
public static void sendCardMessage(String shopId, String cardJson, int sendByUser,
int sendProToRobot, String actionText, String actionTextColor,
String intent, String params)| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| shopId | String | 商户 ID | 标准版使用不带 shopId 的重载;平台版填写目标商户 ID |
| cardJson | String | 自定义卡片的 JSON 内容 | 结构参考卡片 JSON 各类型字段说明 |
| sendByUser | int | 发送方式 | 0:自动发送;1:展示按钮,由访客手动发送;默认为 0 |
| sendProToRobot | int | 机器人阶段是否允许发送 | 0:不发送;1:发送;默认为 0 |
| actionText | String | 手动发送按钮文案 | 可为 null |
| actionTextColor | String | 手动发送按钮颜色 | 可为 null,默认使用后台配置的前景色 |
| intent | String | 意向内容 | 与 UI 展示无关,可为 null |
| params | String | 一触即达流程参数 | 与 UI 展示无关,可为 null |
// 标准版
MessageService.sendCardMessage(cardJson);
// 平台版
MessageService.sendCardMessage(
shopId, cardJson, 0, 0,
"发送", "#337EFF", null, null);打开会话时携带自定义卡片消息
构造 CardDetail 并设置到 ConsultSource.cardDetail。留言状态是否发送由 ConsultSource.isSendProductionLeave 控制;机器人阶段是否发送可以通过 CardDetail 构造参数或 setSendProToRobot() 设置。
String cardJson = "{\"msgtype\":\"cardMessage\",\"cards\":["
+ "{\"type\":\"title\",\"content\":\"订单信息\"},"
+ "{\"type\":\"subtitle\",\"content\":\"订单编号\","
+ "\"subContent\":\"202608180001\"}]}";
CardDetail cardDetail = new CardDetail(cardJson, 0, 0, "发送", "#337EFF");
ConsultSource source = new ConsultSource("order_detail", "订单详情", null);
source.cardDetail = cardDetail;
Unicorn.openServiceActivity(context, "订单咨询", source);| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| cardDetail | CardDetail | 访客发起会话时携带的自定义卡片 | 默认为 null,表示不携带 |
| isSendProductionLeave | boolean | 留言状态是否发送自定义卡片 | 默认为 false,表示不发送 |
CardDetail 配置
CardDetail 提供以下构造方法:
public CardDetail()
public CardDetail(String cardJson, int sendByUser, String actionText, String actionTextColor)
public CardDetail(String cardJson, int sendByUser, int sendProToRobot, String actionText, String actionTextColor)
public CardDetail(String shopId)
public CardDetail(String shopId, String cardJson, int sendByUser, int sendProToRobot, String actionText, String actionTextColor)
public CardDetail(String shopId, String cardJson, int sendByUser, String actionText, String actionTextColor)| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| shopId | String | 商户 ID | 标准版默认为 -1;平台版填写目标商户 ID |
| cardJson | String | 自定义卡片的 JSON 内容 | 结构参考卡片 JSON 各类型字段说明 |
| sendByUser | int | 发送方式 | 0:自动发送;1:展示按钮,由访客手动发送;默认为 0 |
| sendProToRobot | int | 机器人阶段是否允许发送 | 0:不发送;1:发送;默认为 0 |
| actionText | String | 手动发送按钮文案 | 可为 null |
| actionTextColor | String | 手动发送按钮颜色 | 可为 null,默认使用后台配置的前景色 |
| intent | String | 意向内容 | 通过 setIntent 设置,与 UI 展示无关 |
| params | String | 一触即达流程参数 | 通过 setParams 设置,与 UI 展示无关 |
| alwaysSend | boolean | 是否在聊天过程中继续发送 | 通过 setAlwaysSend 设置,默认为 false |
商品/订单组件白色气泡
从 Android SDK 11.8.0 开始,可以通过 YSFOptions.productOrderCardMessageWhiteBubbleEnabled 控制自定义卡片消息的气泡背景。该开关仅在 cardJson 中至少包含一个 type: "product" 或 type: "order" 组件时生效。
| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| productOrderCardMessageWhiteBubbleEnabled | boolean | 包含商品或订单组件的自定义卡片消息是否使用白色气泡 | 默认为 false,继续使用原有样式并受后台 UI 配置影响;设置为 true 后,发出和收到的卡片都使用白色气泡 |
建议在初始化 SDK 时设置;运行期间修改同一个 YSFOptions 对象后,也可以调用 Unicorn.updateOptions(options) 更新配置。
YSFOptions options = new YSFOptions();
options.productOrderCardMessageWhiteBubbleEnabled = true;
Unicorn.config(context, appKey, options, imageLoader);
// SDK 已初始化时更新
options.productOrderCardMessageWhiteBubbleEnabled = false;
Unicorn.updateOptions(options);卡片 JSON 各类型字段说明
cardJson 的基础结构如下,下面各类型字段均为 cards 数组中的组件对象:
{
"msgtype": "cardMessage",
"cards": [
{
"type": "title",
"content": "订单信息"
}
]
}标题类型
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,title |
| content | 是 | String | 标题 |
| img | 否 | String | 左侧图片URL |
| url | 否 | String | 链接跳转地址 |
| style | 否 | JSONObject | 样式 |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| fontSize | 否 | String | 文字大小,默认14px |
| fontColor | 否 | String | 字体颜色,有默认值 |
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
副标题文本
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,subtitle |
| content | 是 | String | 左侧文字内容 |
| subContent | 否 | String | 右侧文字内容 |
| img | 否 | String | 图片url地址,默认与文字等高 |
| url | 否 | String | 跳转超链接 |
| style | 否 | JSONObject | 样式 |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| backgroundColor | 否 | String | 背景色 |
| fontSize | 否 | String | 左侧文字大小,有默认值 |
| fontColor | 否 | String | 左侧文字字体颜色,有默认值 |
| subFontSize | 否 | String | 右侧文字大小,有默认值 |
| subFontColor | 否 | String | 右侧文字字体颜色,有默认值 |
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
富文本
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,rich |
| content | 是 | String | 内容 |
| style | 否 | JSONObject | 样式 |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
图片
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,image |
| img | 是 | String | 图片地址 |
| url | 否 | String | 跳转超链接 |
| style | 否 | JSONObject | 样式 |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
商品
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,product |
| data | 是 | JSONArray | 商品数据节点 |
| style | 否 | JSONObject | 样式 |
data字段JSONArray结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| id | 是 | String | 商品id |
| picture | 是 | String | 商品图片 |
| desc | 是 | String | 描述 |
| title | 是 | String | 标题 |
| price | 是 | String | 价格 |
| url | 否 | String | 跳转超链接,action为0的情况下的跳转链接 |
| action | 否 | int | 2种类型,默认为0 (0 :点击跳转url, 1 :点击发送对应的商品卡片 (只适用于访客端) |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
订单
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,order |
| orderId | 是 | String | 订单ID |
| orderTime | 是 | String | 下单时间 |
| data | 是 | JSONArray | 订单数据节点 |
| style | 否 | JSONObject | 样式 |
data字段JSONArray结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| picture | 是 | String | 商品图片 |
| desc | 是 | String | 描述 |
| title | 是 | String | 商品标题 |
| status | 是 | String | 交易状态 |
| payMoney | 否 | String | 价格 |
| url | 否 | String | 跳转超链接,action为0的情况下的跳转链接 |
| count | 否 | String | 订单商品数量 x 100 |
| sku | 否 | String | 商品sku描述 |
| action | 否 | int | 2种类型,默认为0 (0 :点击跳转url, 1 :点击发送对应的订单卡片 (只适用于访客端)) |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
节点流程
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,flow |
| data | 是 | JSONArray | 流程数据节点 |
| style | 否 | JSONObject | 样式 |
data字段JSONArray结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| id | 是 | String | 流程id |
| title | 是 | String | 标题 |
| subtitle | 否 | String | 副标题 |
| current | 否 | int | 2种状态 (0 :非当前步骤,1 :当前步骤) |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| fontSize | 否 | String | 主标题文字大小,有默认值 |
| fontColor | 否 | String | 主标题文字字体颜色,有默认值 |
| subFontSize | 否 | String | 副标题文字大小,有默认值 |
| subFontColor | 否 | String | 副标题文字字体颜色,有默认值 |
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
操作按钮
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,button |
| numInRow | 否 | int | 一行显示几个按钮 默认1个(悬浮组件不生效) |
| data | 是 | JSONArray | 按钮数据节点 |
| style | 否 | JSONObject | 样式 |
data字段JSONArray结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| title | 是 | String | 商品标题 |
| action | 否 | int | 2种类型,默认为0 (0 点击跳转链接, 1 点击发送配置的文字 (只适用于访客端)) |
| url | 否 | String | 跳转超链接,action为0的情况下的跳转链接 |
| content | 否 | String | 发送内容,action为1的情况下的发送内容 |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| backgroundColor | 否 | String | 背景色 |
| fontSize | 否 | String | 文字大小,有默认值 |
| fontColor | 否 | String | 文字字体颜色,有默认值 |
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
跳转链接
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| type | 是 | String | 固定值,link |
| content | 是 | String | 标题 |
| url | 是 | String | 跳转链接 |
| style | 否 | JSONObject | 样式 |
style字段JSONObject结构
| 参数 | 是否必须 | 参数类型 | 参数说明 |
|---|---|---|---|
| fontSize | 否 | JSONObject | 文字大小,有默认值 |
| fontColor | 否 | JSONObject | 文字字体颜色,有默认值 |
| pt | 否 | int | 上边距 |
| pb | 否 | int | 下边距 |
| pl | 否 | int | 左边距 |
| pr | 否 | int | 右边距 |
| divider | 否 | int | 是否需要分割线,0:不需要;1:需要 |
参考实现
完整 JSON 可以按上述组件字段自由组合,也可以参考官网 Demo 源码中的 CardMessageHelper 类。
组合效果展示
可以通过以上开放的模板进行自由组合,生成半自定义的消息体样式,一条卡片消息可以包含多种类型的卡片。 以下为部分举例示意:
代码示例
String cardJson = "{\"msgtype\":\"cardMessage\",\"cards\":["
+ "{\"type\":\"title\",\"content\":\"订单信息\"},"
+ "{\"type\":\"subtitle\",\"content\":\"订单编号\","
+ "\"subContent\":\"202608180001\"}]}";
CardDetail cardDetail = new CardDetail(cardJson, 0, 0, "发送", "#337EFF");
// 其他字段可以通过 setter 设置
cardDetail.setIntent("intent");
cardDetail.setParams("params");
source.cardDetail = cardDetail;自定义 Fragment 接入方式下的视频、图片和文件
手动启动 SDK 的视频和图片界面
SDK 增加了启动拍摄视频、拍摄照片、选择本地视频、选择本地照片的四个方法。
用户可以通过相关方法启动 SDK 中的界面,然后通过相关回调发送图片和视频,具体操作分为三步:
第一步:创建 UnicornVideoPickHelper 和 UnicornPickImageHelper 并在初始化方法中加入回调
第二步:通过 UnicornVideoPickHelper 和 UnicornPickImageHelper 启动 SDK 原生视频或相册界面
第三步:在 Activity 中通过 onActivityResult 方法把视频和图片回传给 UnicornVideoPickHelper 和 UnicornPickImageHelper
代码示例
//第一步
//当使用视频相关功能时,创建 UnicornVideoPickHelper
unicornVideoPickHelper = new UnicornVideoPickHelper(this, new UnicornVideoPickHelper.UincornVideoSelectListener() {
@Override
public void onVideoPicked(File file, String md5) {
//当视频处理成功的时候,我们需要去发送视频类型的消息
MediaPlayer mediaPlayer = getVideoMediaPlayer(file);
long duration = mediaPlayer == null ? 0 : mediaPlayer.getDuration();
int height = mediaPlayer == null ? 0 : mediaPlayer.getVideoHeight();
int width = mediaPlayer == null ? 0 : mediaPlayer.getVideoWidth();
IMMessage message = UnicornMessageBuilder.buildVideoMessage(file, duration, width, height, md5);
MessageService.sendMessage(message);
}
});
//当使用图片相关功能时,创建 UnicornPickImageHelper
unicornPickImageHelper = new UnicornPickImageHelper(this, new UnicornPickImageHelper.SendImageCallback() {
@Override
public void sendImage(File file, String origName, boolean isOrig) {
//当图片处理成功的时候,发送图片类型的消息
IMMessage message = UnicornMessageBuilder.buildImageMessage(UnicornMessageBuilder.getSessionId(), file, file.getName());
MessageService.sendMessage(message);
}
});
//第二步,在 APP 想要启动 SDK 界面的时候,调用相关方法启动
private void addPhoneMenu() {
addImageMenu(R.drawable.ic_action_album).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动选择相册图片
unicornPickImageHelper.goUnicornAlbum(ALBUM_IMAGE_REQUEST_CODE);
}
});
addImageMenu(R.drawable.ic_action_camera).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动拍摄照片
unicornPickImageHelper.goUnicornCapturePhoto(CAPTURE_IMAGE_REQUEST_CODE);
}
});
addImageMenu(R.drawable.ic_action_take_video).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动拍摄视频界面
unicornVideoPickHelper.goCaptureVideo(CAPTURE_VIDEO_REQUEST_CODE);
}
});
addImageMenu(R.drawable.ic_action_select_video).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//启动选择视频界面
unicornVideoPickHelper.goVideoAlbum(LOCAL_VIDEO_REQUEST_CODE);
}
});
}
//第三步,在 onActivityResult 中把数据回传给 SDK
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CAPTURE_VIDEO_REQUEST_CODE:
//拍摄视频使用
unicornVideoPickHelper.onCaptureVideoResult(data);
break;
case LOCAL_VIDEO_REQUEST_CODE:
//从相册中选择视频使用
unicornVideoPickHelper.onSelectLocalVideoResult(data);
break;
case ALBUM_IMAGE_REQUEST_CODE:
//从相册中选择照片使用
unicornPickImageHelper.onAlbumResult(data);
break;
case CAPTURE_IMAGE_REQUEST_CODE:
//拍照中使用
unicornPickImageHelper.onCapturePhotoResult(data, CAPTURE_IMAGE_PROCESS_REQUEST_CODE);
break;
case CAPTURE_IMAGE_PROCESS_REQUEST_CODE:
//拍照中使用
unicornPickImageHelper.onCapturePhotoPorcessResult(data, CAPTURE_IMAGE_REQUEST_CODE);
break;
}
}从 Android SDK 11.8.0 开始,Fragment 使用静态视频选择入口时,可以直接把系统返回的 Intent 交给以下重载方法:
public static void onSelectLocalVideoResult(
Intent data,
UnicornVideoPickHelper.UincornVideoSelectListener listener)该方法同时处理 Intent.getClipData() 返回的多选结果和 Intent.getData() 返回的单选结果,最多处理 9 个视频,并对每个通过文件复制和大小校验的视频调用一次 onVideoPicked。
// 在 Fragment 中打开视频选择页
UnicornVideoPickHelper.goVideoAlbumActivity(this, LOCAL_VIDEO_REQUEST_CODE);
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == LOCAL_VIDEO_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
UnicornVideoPickHelper.onSelectLocalVideoResult(
data,
new UnicornVideoPickHelper.UincornVideoSelectListener() {
@Override
public void onVideoPicked(File file, String md5) {
// 使用 UnicornMessageBuilder 构造视频消息并发送
}
});
}
}完整接入代码可参考 Demo 工程中的 ServiceActivity。
工单能力
本节说明在 Android SDK 中打开工单填写、工单列表和催单入口的方式。
打开工单填写弹窗
Fragment 接入方式下,可以通过 UnicornWorkSheetHelper 打开工单填写弹窗,并在 onActivityResult 中回传附件选择结果。
public void openWorkSheetDialog(long templateId, String exchange, int watchAnnexRequestCode, int selectAnnexRequestCode, RequestCallback<String> requestCallback)
public void onResultWorkSheet(int requestCode, Intent data)| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| templateId | long | 工单模板 ID | 可通过 Web 端客服工作台获取 |
| exchange | String | 会话标识 | 平台版本传 shopId;非平台版本可通过 UnicornMessageBuilder.getSessionId() 获取 |
| watchAnnexRequestCode | int | 预览附件请求码 | 接入方自定义 |
| selectAnnexRequestCode | int | 选择附件请求码 | 接入方自定义 |
| requestCallback | RequestCallback<String> | 提交结果回调 | 提交成功或失败时回调 |
代码示例
UnicornWorkSheetHelper helper = new UnicornWorkSheetHelper(fragment);
helper.openWorkSheetDialog(templateId, exchange, WATCH_ANNEX_REQUEST_CODE, SELECT_ANNEX_REQUEST_CODE, new RequestCallback<String>() {
@Override
public void onSuccess(String result) {
// 工单提交成功
}
@Override
public void onFailed(int code) {
// 工单提交失败
}
@Override
public void onException(Throwable exception) {
// 异常处理
}
});打开用户工单列表
如需打开用户工单列表,可调用静态方法 openUserWorkSheetActivity。
public static void openUserWorkSheetActivity(Context context, List<Long> templateIds, boolean isOpenUrge, String exchange)| 参数名 | 类型 | 参数说明 | 取值说明 |
|---|---|---|---|
| context | Context | 上下文 | 当前页面 Context |
| templateIds | List<Long> | 工单模板 ID 列表 | 可通过 Web 端客服工作台获取 |
| isOpenUrge | boolean | 是否打开催单功能 | true 表示打开 |
| exchange | String | 会话标识 | 平台版本传 shopId;非平台版本可通过 UnicornMessageBuilder.getSessionId() 获取 |
代码示例
List<Long> templateIds = new ArrayList<>();
templateIds.add(templateId);
UnicornWorkSheetHelper.openUserWorkSheetActivity(context, templateIds, true, UnicornMessageBuilder.getSessionId());输入栏工单入口
如需在输入栏更多面板中增加工单入口,可使用 InquireWorkSheetAction。
InquireWorkSheetAction action = new InquireWorkSheetAction(R.drawable.ic_work_sheet, "工单");
action.setTemplateIds(templateIds);
action.setOpenUrge(true);这篇文档有帮助吗?