iOS

最近更新时间:2024-02-29 14:40:42

我的收藏

前提条件

了解在线客服相关术语及相关配置,并已完成以下步骤:添加客服、配置技能组、创建会话服务流,详情请参见 快速入门
集成 IM SDK ,并跑通登录、发单聊消息等功能。请参见 iOS(含 UI)

UI 插件介绍

您可以集成腾讯云即时通信官方提供的在线客服插件 TUICustomerServicePlugin,集成后,您可以在 IM 应用中集成在线客服功能。在线客服功能包括解析在线客服自定义消息、发送卡片消息、主动评价客服等。

环境与版本

本插件依赖插件以及环境
Xcode 10 及以上
iOS 9.0 及以上

插件集成

通过 CocoaPods 可将在线客服插件引入到项目中:
# 集成客服插件(7.6.5011 及以上版本支持)
pod 'TUICustomerServicePlugin'
# 客服插件依赖 TUIChat 和 TUIContact 两个组件,这两个组件也需同时集成,客服插件版本需要和组件版本保持一致
pod 'TUIChat'
pod 'TUIContact'

步骤1:管理端客服插件设置

首先请您完成客服插件的 管理端配置

步骤2:终端客服列表设置

在进入客服虚拟号的聊天页面时会有特殊的消息发送操作,因此我们建议在终端将客服虚拟号配置保存起来,方便全局调用。
// 1. 设置默认的客服虚拟号
// 代码参考 TUICustomerServicePluginPrivateConfig.m 中的 customerServiceAccounts 函数
@implementation TUICustomerServicePluginPrivateConfig
- (NSArray *)customerServiceAccounts {
return @[gOnlineShopping, gOnlineDoctor];
}
@end
// 2. 监听 TUIContact 组件的 TUICore_TUIContactExtension_ContactMenu_ClassicExtensionID 扩展事件添加客服到联系人列表中
// 代码参考 TUICustomerServicePluginExtensionObserver.m 的 load 函数和 onGetExtension 函数
@implementation TUICustomerServicePluginExtensionObserver
+ (void)load {
[TUICore registerExtension:TUICore_TUIContactExtension_ContactMenu_ClassicExtensionID object:TUICustomerServicePluginExtensionObserver.shareInstance];
}
- (NSArray<TUIExtensionInfo *> *)onGetExtension:(NSString *)extensionID param:(NSDictionary *)param {
if (![extensionID isKindOfClass:NSString.class]) {
return nil;
}
if ([extensionID isEqualToString:TUICore_TUIContactExtension_ContactMenu_ClassicExtensionID]) {
TUIExtensionInfo *customerService = [[TUIExtensionInfo alloc] init];
customerService.weight = 50;
customerService.text = TIMCommonLocalizableString(TUICustomerServiceAccounts);
customerService.icon = TUICustomerServicePluginBundleThemeImage(@"customer_service_contact_menu_icon_img", @"contact_customer_service");
customerService.onClicked = ^(NSDictionary *_Nonnull param) {
// 在线客服被点击后事件处理
};
return @[customerService];
}
}
// 3. 获取在线客服的资料信息
// 代码参考 TUICustomerServicePluginAccountController 的 getUserInfo 函数
@implementation TUICustomerServicePluginAccountController
- (void)getUserInfo {
NSArray *accounts = TUICustomerServicePluginPrivateConfig.sharedInstance.customerServiceAccounts;
[[V2TIMManager sharedInstance] getUsersInfo:accounts
succ:^(NSArray<V2TIMUserFullInfo *> *infoList) {
for (V2TIMUserFullInfo *info in infoList) {
TUICommonContactCellData *data = [TUICommonContactCellData new];
data.identifier = info.userID;
data.avatarUrl = [NSURL URLWithString:info.faceURL];
data.title = info.nickName;
}
} fail:^(int code, NSString *desc) {
// 获取资料信息失败
}];
}
@end

步骤3:在线客服消息解析

在线客服的特殊消息,例如分支消息卡片消息、评价消息等,都是通过自定义消息实现,因此您需要在接收消息时判断自定义消息类型并解析渲染。TUICustomerServicePlugin 组件提供了各种 CellDataCell 来解析和渲染不同的自定义消息。使用方法如下:
// 1. 注册客服自定义消息,绑定自定义消息的 BussinessID 和消息解析以及渲染的 CellData 和 Cell
// TUIChat 组件收到客服自定义消息,会根据 BussinessID 自动调用 CellData 的 getCellData 函数解析消息,调用 Cell 的 fillWithData 函数渲染消息
// 代码参考 TUICustomerServicePluginService 的 registerCustomMessageCell 函数
@implementation TUICustomerServicePluginService
- (void)registerCustomMessageCell {
// 分支选择自定义消息
[TUICore callService:TUICore_TUIChatService
method:TUICore_TUIChatService_AppendCustomMessageMethod
param:@{BussinessID : BussinessID_Src_CustomerService_Branch,
TMessageCell_Name : @"TUICustomerServicePluginBranchCell",
TMessageCell_Data_Name : @"TUICustomerServicePluginBranchCellData"
}
];
// 表单收集自定义消息
[TUICore callService:TUICore_TUIChatService
method:TUICore_TUIChatService_AppendCustomMessageMethod
param:@{BussinessID : BussinessID_Src_CustomerService_Collection,
TMessageCell_Name : @"TUICustomerServicePluginCollectionCell",
TMessageCell_Data_Name : @"TUICustomerServicePluginCollectionCellData"
}
];
//...
}
@end
// 2. 通过 CellData 解析自定义消息
@implementation TUICustomerServicePluginXXXCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
// 解析自定义消息
}
@end
// 3. 通过 Cell 展示自定义消息
@implementation TUICustomerServicePluginXXXCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// 初始化 Cell
}
return self;
}
- (void)fillWithData:(TUICustomerServicePluginCollectionCellData *)data {
[super fillWithData:data];
// 根据 data 绘制 Cell
}
在线客服消息中有一些特殊的消息为标志消息,例如会话结束、会话开始、客服配置等,这一类消息不需要渲染在消息列表中。CellData 提供了 shouldHide 方法用于判断在线客服消息是否需要在消息列表中渲染。使用方法如下:
// 参考 TUICustomerServicePluginInvisibleCellData 的 shouldHide 函数
@implementation TUICustomerServicePluginXXXCellData
- (BOOL)shouldHide {
return YES;
}
@end

步骤4:主动发送启动消息

在进入在线客服用户的聊天时,需要发送一条特殊的自定义消息用于启动在线客服会话。TUICustomerServicePlugin 提供了 sendCustomMessageWithoutUpdateUI 方法用于快速发送启动消息。使用方法如下:
// 1. 注册聊天界面进入事件监听
// 参考 TUICustomerServicePluginService.m 的 registerEvent 函数
@implementation TUICustomerServicePluginService
- (void)registerEvent {
[TUICore registerEvent:TUICore_TUIChatNotify
subKey:TUICore_TUIChatNotify_ViewDidLoadSubKey
object:self];
}
// 2. 收到监听回调后,主动发送一条特殊的自定义消息启动在线客服会话
// 参考 TUICustomerServicePluginService.m 的 onNotifyEvent 函数
- (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(nullable id)anObject param:(nullable NSDictionary *)param {
if ([key isEqualToString:TUICore_TUIChatNotify] &&
[subKey isEqualToString:TUICore_TUIChatNotify_ViewDidLoadSubKey]) {
if (param == nil) {
NSLog(@"TUIChat notify param is invalid");
return;
}
NSString *userID = [param objectForKey:TUICore_TUIChatNotify_ViewDidLoadSubKey_UserID];
if (![TUICustomerServicePluginPrivateConfig.sharedInstance isCustomerServiceAccount:userID]) {
return;
}
NSData *data = [TUITool dictionary2JsonData:@{@"src": BussinessID_Src_CustomerService_Request}];
[TUICustomerServicePluginDataProvider sendCustomMessageWithoutUpdateUI:data];
}
}
@end

获取在线客服输入状态(可选)

当座席端正在输入消息时,用户端可以收到下发的自定义消息用于确定客服的输入状态。TUICustomerServicePlugin 提供了TUICustomerServicePluginTypingCellData 类用于解析自定义消息,使用方法如下:
// 1. 注册座席端正在输入自定义消息,绑定自定义消息的 BussinessID 和消息解析的 CellData
// TUIChat 组件收到自定义消息,会根据 BussinessID 自动调用 CellData 的 getCellData 函数解析消息
// 代码参考 TUICustomerServicePluginService 的 registerCustomMessageCell 函数
@implementation TUICustomerServicePluginService
- (void)registerCustomMessageCell {
// 座席端正在输入自定义消息
[TUICore callService:TUICore_TUIChatService
method:TUICore_TUIChatService_AppendCustomMessageMethod
param:@{BussinessID : BussinessID_Src_CustomerService_Typing,
TMessageCell_Name : @"TUIMessageCell",
TMessageCell_Data_Name : @"TUICustomerServicePluginTypingCellData"
}
];
}
@end
// 2. 通过 CellData 解析自定义消息
// 参考 TUICustomerServicePluginTypingCellData.m 的 getCellData 函数
// 其中 TUICustomerServicePluginTypingCellData 需要继承自 TUITypingStatusCellData,并设置对方正在输入状态(typingStatus)
@interface TUICustomerServicePluginTypingCellData : TUITypingStatusCellData
@end
@implementation TUICustomerServicePluginTypingCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
TUITypingStatusCellData *cellData = [[TUITypingStatusCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.msgID = message.msgID;
if ([param[@"src"] isEqualToString: BussinessID_Src_CustomerService_Typing]) {
cellData.typingStatus = 1;
}
return cellData;
}
@end
// 3.TUIChat 组件根据 CellData 的 typingStatus 状态自动在聊天界面 NavgationBar 展示或隐藏 "对方正在输入..." 状态

主动发送卡片消息(可选)

在与在线客服的对话中,用户端可主动发送带跳转地址的卡片消息。卡片消息包括名称、描述、图片、点击时的跳转地址四个属性。




1.添加入快速操作栏




您可以将发送卡片消息的快捷按钮放在聊天页面中,具体实现参考如下:
// 1. 注册聊天页面快捷操作栏扩展事件
// 代码参考 TUICustomerServicePluginExtensionObserver 的 load 函数
@implementation TUICustomerServicePluginExtensionObserver
+ (void)load {
[TUICore registerExtension:TUICore_TUIChatExtension_ChatVCBottomContainer_ClassicExtensionID object:TUICustomerServicePluginExtensionObserver.shareInstance];
}
// 2. 在事件回调添加快捷操作栏
// 代码参考 TUICustomerServicePluginExtensionObserver 的 onRaiseExtension 函数
- (BOOL)onRaiseExtension:(NSString *)extensionID parentView:(UIView *)parentView param:(nullable NSDictionary *)param {
if ([extensionID isEqualToString:TUICore_TUIChatExtension_ChatVCBottomContainer_ClassicExtensionID]) {
if (param == nil) {
NSLog(@"TUIChat notify param is invalid");
return NO;
}
NSString *userID = [param objectForKey:TUICore_TUIChatExtension_ChatVCBottomContainer_UserID];
if (![TUICustomerServicePluginPrivateConfig.sharedInstance isOnlineShopping:userID]) {
return NO;
}
if (![parentView isKindOfClass:UIView.class]) {
return NO;
}
TUICustomerServicePluginMenuView *view = [[TUICustomerServicePluginMenuView alloc] initWithDataSource:TUICustomerServicePluginConfig.sharedInstance.menuItems];
[parentView addSubview:view];
[view updateFrame];
return YES;
}
return NO;
}
@end
// 3. 自定义快捷操作栏为 "发送商品"
// 代码参考 TUICustomerServicePluginConfig 的 defaultMenuItems 函数
@implementation TUICustomerServicePluginConfig
- (NSArray *)defaultMenuItems {
NSMutableArray *dataSource = [NSMutableArray new];
TUICustomerServicePluginMenuCellData *product = [TUICustomerServicePluginMenuCellData new];
product.title = TIMCommonLocalizableString(TUICustomerServiceSendProduct);
product.target = TUICustomerServicePluginExtensionObserver.shareInstance;
product.cselector = @selector(onProductClicked);
[dataSource addObject:product];
return [dataSource copy];
}
@end

2.发送卡片消息

上一步骤注册了发送卡片的快捷入口后,在快捷入口被点击后会触发 onProductClicked 回调,您可以在该回调下发送卡片消息:
// 1. 快捷入口被点击
// 代码参考 TUICustomerServicePluginExtensionObserver 的 onProductClicked 函数
@implementation TUICustomerServicePluginExtensionObserver
- (void)onProductClicked {
TUICustomerServicePluginProductInfo *info = TUICustomerServicePluginConfig.sharedInstance.productInfo;
NSDictionary *dict = @{BussinessID_CustomerService: @0,
@"src": BussinessID_Src_CustomerService_Card,
@"content": @{@"header": info.title ?: @"",
@"desc": info.desc ?: @"",
@"pic": info.picURL ?: @"",
@"url": info.linkURL ?: @""}
};
NSData *data = [TUITool dictionary2JsonData:dict];
[TUICustomerServicePluginDataProvider sendCustomMessage:data];
}
@end
// 2. 自定义商品信息
// productInfo 的组成方式可以参考 TUICustomerServicePluginConfig 的 defaultProductInfo 函数
@implementation TUICustomerServicePluginConfig
- (TUICustomerServicePluginProductInfo *)defaultProductInfo {
TUICustomerServicePluginProductInfo *info = [TUICustomerServicePluginProductInfo new];
info.title = @"手工编织皮革提包2023新品女士迷你简约大方高端有档次";
info.desc = @"¥788";
info.picURL = @"https://qcloudimg.tencent-cloud.cn/raw/a811f634eab5023f973c9b224bc07a51.png";
info.linkURL = @"https://cloud.tencent.com/document/product/269";
return info;
}
@end

3.展示卡片消息

上一步骤发送了卡片消息后,我们需要在聊天界面展示卡片消息,可以参考如下代码实现:
// 1. 注册卡片自定义消息,绑定自定义消息的 BussinessID 和消息解析以及渲染的 CellData 和 Cell
// TUIChat 组件收到自定义消息,会根据 BussinessID 自动调用 CellData 的 getCellData 函数解析消息,调用 Cell 的 fillWithData 函数渲染消息
// 代码参考 TUICustomerServicePluginService 的 registerCustomMessageCell 函数
@implementation TUICustomerServicePluginService
- (void)registerCustomMessageCell {
// 卡片自定义消息
[TUICore callService:TUICore_TUIChatService
method:TUICore_TUIChatService_AppendCustomMessageMethod
param:@{BussinessID : BussinessID_Src_CustomerService_Card,
TMessageCell_Name : @"TUICustomerServicePluginCardCell",
TMessageCell_Data_Name : @"TUICustomerServicePluginCardCellData"
}
];
}
@end
// 2. 通过 CellData 解析卡片消息
// 代码参考 TUICustomerServicePluginCardCellData.m 的 getCellData 函数
@implementation TUICustomerServicePluginCardCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data
options:NSJSONReadingAllowFragments error:nil];
if (param == nil) {
return nil;
}
TUICustomerServicePluginCardCellData *cellData = [[TUICustomerServicePluginCardCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.innerMessage = message;
NSDictionary *content = param[@"content"];
cellData.header = content[@"header"];
cellData.desc = content[@"desc"];
cellData.picURL = content[@"pic"];
cellData.jumpURL = content[@"url"];
return cellData;
}
@end
// 3. 通过 Cell 展示卡片消息
// 代码参考 TUICustomerServicePluginCardCell.m 的 initWithStyle 和 fillWithData 函数
@implementation TUICustomerServicePluginCardCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.headerLabel = [[UILabel alloc] init];
[self.container addSubview:self.headerLabel];
self.descLabel = [[UITableView alloc] init];
[self.container addSubview:self.descLabel];
//...
}
return self;
}
- (void)fillWithData:(TUICustomerServicePluginCardCellData *)data {
[super fillWithData:data];
self.customData = data;
self.headerLabel.text = data.header;
self.descLabel.text = data.desc;
//...
}
@end

主动评价客服(可选)

当在在线客服的管理端设置了用户可主动评价坐席时,用户端就可以主动拉取评价消息并评价。



TUICustomerServicePluginPrivateConfig 提供了 canEvaluate 参数用于快速判断是否能主动发送评价, canEvaluate 参数的赋值方法如下:
// 1. 注册评价规则自定义消息,绑定自定义消息的 BussinessID 和消息解析以及渲染的 CellData 和 Cell
// TUIChat 组件收到评价规则自定义消息(主动发送启动消息后会收到),会根据 BussinessID 自动调用 CellData 的 getCellData 函数解析消息
// 代码参考 TUICustomerServicePluginService 的 registerCustomMessageCell 函数
@implementation TUICustomerServicePluginService
- (void)registerCustomMessageCell {
// 评价规则自定义消息
[TUICore callService:TUICore_TUIChatService
method:TUICore_TUIChatService_AppendCustomMessageMethod
param:@{BussinessID : BussinessID_Src_CustomerService_EvaluationRule,
TMessageCell_Name : @"TUICustomerServicePluginInvisibleCell",
TMessageCell_Data_Name : @"TUICustomerServicePluginInvisibleCellData"
}
];
}
@end
// 2. 通过 CellData 解析自定义消息,判断是否可以主动发送评价
@implementation TUICustomerServicePluginInvisibleCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data
options:NSJSONReadingAllowFragments error:nil];
if (param == nil) {
return nil;
}
TUICustomerServicePluginInvisibleCellData *cellData = [[TUICustomerServicePluginInvisibleCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.innerMessage = message;
if ([param[@"src"] isEqualToString: BussinessID_Src_CustomerService_EvaluationRule]) {
NSDictionary *content = param[@"content"];
// 判断是否能发送评价消息
NSInteger menuSendRuleFlag = [content[@"menuSendRuleFlag"] integerValue];
[TUICustomerServicePluginPrivateConfig sharedInstance].canEvaluate = menuSendRuleFlag >> 2;
}
return cellData;
}
@end
如果可以主动发送评价,我们可以注册输入栏的快捷入口,当入口被点击后,主动发送评价请求自定义消息。



// 1. 注册输入栏快捷入口扩展监听
// 参考 TUICustomerServicePluginExtensionObserver 的 load 函数
@implementation TUICustomerServicePluginExtensionObserver
+ (void)load {
[TUICore registerExtension:TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID object:TUICustomerServicePluginExtensionObserver.shareInstance];
}
// 2. 在监听回调注册 "服务评价" 按钮
// 参考 TUICustomerServicePluginExtensionObserver 的 onGetExtension 函数
- (NSArray<TUIExtensionInfo *> *)onGetExtension:(NSString *)extensionID param:(NSDictionary *)param {
if (![extensionID isKindOfClass:NSString.class]) {
return nil;
}
if ([extensionID isEqualToString:TUICore_TUIChatExtension_InputViewMoreItem_ClassicExtensionID]) {
NSString *userID = [param tui_objectForKey:TUICore_TUIChatExtension_InputViewMoreItem_UserID asClass:NSString.class];
if (![TUICustomerServicePluginPrivateConfig.sharedInstance isCustomerServiceAccount:userID]) {
return nil;
}
if (![TUICustomerServicePluginPrivateConfig sharedInstance].canEvaluate) {
return nil;
}
TUIExtensionInfo *evaluation = [[TUIExtensionInfo alloc] init];
evaluation.weight = 100;
evaluation.text = TIMCommonLocalizableString(TUIKitMoreEvaluation);
evaluation.icon = TIMCommonBundleThemeImage(@"service_more_customer_service_evaluation_img", @"more_customer_service_evaluation");
evaluation.onClicked = ^(NSDictionary *_Nonnull param) {
// 3. "服务评价" 按钮被点击后发送主动评价请求信令
NSData *data = [TUITool dictionary2JsonData:@{@"src": BussinessID_Src_CustomerService_EvaluationTrigger}];
[TUICustomerServicePluginDataProvider sendCustomMessageWithoutUpdateUI:data];
};
return @[evaluation];
} else {
return nil;
}
}
@end
服务器收到终端发送的评价请求自定义消息,会下发评价消息,终端收到评价消息后解析展示:
// 1. 注册评价自定义消息,绑定自定义消息的 BussinessID 和消息解析以及渲染的 CellData 和 Cell
// TUIChat 组件收到自定义消息,会根据 BussinessID 自动调用 CellData 的 getCellData 函数解析消息,调用 Cell 的 fillWithData 函数渲染消息
// 代码参考 TUICustomerServicePluginService 的 registerCustomMessageCell 函数
@implementation TUICustomerServicePluginService
- (void)registerCustomMessageCell {
// 评价自定义消息
[TUICore callService:TUICore_TUIChatService
method:BussinessID_Src_CustomerService_Evaluation
param:@{BussinessID : BussinessID_Src_CustomerService_Card,
TMessageCell_Name : @"TUICustomerServicePluginEvaluationCell",
TMessageCell_Data_Name : @"TUICustomerServicePluginEvaluationCellData"
}
];
}
@end
// 2. 通过 CellData 解析评价消息
// 参考 TUICustomerServicePluginEvaluationCellData.m 的 getCellData 函数
@implementation TUICustomerServicePluginEvaluationCellData
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data
options:NSJSONReadingAllowFragments error:nil];
if (param == nil) {
return nil;
}
TUICustomerServicePluginEvaluationCellData *cellData = [[TUICustomerServicePluginEvaluationCellData alloc] initWithDirection:message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming];
cellData.innerMessage = message;
NSDictionary *content = param[@"menuContent"];
cellData.type = [content[@"type"] integerValue];
cellData.header = content[@"head"];
cellData.tail = content[@"tail"];
//...
return cellData;
}
@end
// 3. 通过 Cell 展示评价消息
// 参考 TUICustomerServicePluginEvaluationCell.m 的 initWithStyle 和 fillWithData 函数
@implementation TUICustomerServicePluginEvaluationCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.headerLabel = [[UILabel alloc] init];
[self.container addSubview:self.headerLabel];
self.bottomLabel = [[UILabel alloc] init];
[self.container addSubview:self.bottomLabel];
//...
}
return self;
}
- (void)fillWithData:(TUICustomerServicePluginCollectionCellData *)data {
[super fillWithData:data];
self.customData = data;
self.headerLabel.text = data.header;
self.bottomLabel.text = data.tail;
//...
}
@end