# 客服功能说明
# CEFWebBrowserWindow.cpp(CEF 后端)
FCEFWebBrowserWindow::OnFileDialog()
文件:CEFWebBrowserWindow.cpp;修改函数:FCEFWebBrowserWindow::OnFileDialog()。
引擎相对路径:
Engine/Source/Runtime/WebBrowser/Private/CEF/CEFWebBrowserWindow.cpp
搜索 FCEFWebBrowserWindow::OnFileDialog()。当前使用的 UE 5.6.1 源码引擎中,函数从第 1416 行开始,修改位置为第 1425~1427 行;其他引擎版本应按函数名定位,保留其原有函数签名。
修改后(函数体):
/* 删除代码:解除文件选择框拦截。
UE_LOG(LogWebBrowser, Error, TEXT("FileDialogs are prevented."));
return true;
*/
// 新增代码:交给 CEF 默认处理文件选择对话框。
return false;
2
3
4
5
6
7
目标引擎若仍有上述拦截实现,需要修改引擎源码并重新编译,仅替换 SDK 插件无法使此补丁生效。
# 鸿蒙平台引擎修改
以下五处修改用于修复当前 UE 5.6.1 鸿蒙源码引擎中的浏览器显示隐藏问题。其他引擎版本请按文件和函数名检查;已包含对应修复时无需重复修改。以下路径均相对于引擎根目录。
鸿蒙平台需要同时完成函数绑定、状态同步和组件标识修改。仅补充空指针检查能够避免该处崩溃,但不能解决浏览器黑屏。
# AkiJSBind.ets.template
# AkiJSBind.MainThreadBindAki()
文件:AkiJSBind.ets.template;修改函数:AkiJSBind.MainThreadBindAki()。
引擎相对路径:
Engine/Build/OpenHarmony/entry/src/main/ets/common/AkiJSBind.ets.template
定位 MainThreadBindAki() 的 WebView 绑定区域,在已有注册后增加 WebView.setVisible。以下为局部绑定代码,保留函数中的其他注册。
修改后:
nativeRender.JSBind.bindFunction("WebView.reload", WebView.reload);
// 新增代码:注册浏览器可见性函数。
nativeRender.JSBind.bindFunction("WebView.setVisible", WebView.setVisible);
2
3
4
WebView.setVisible 的实现已存在,此处补齐 C++ 调用所需的绑定。
# OpenHarmonyArkTSWebBrowser.cpp
# FArkTSOpenHarmonyWebBrowser::SetVisibility()
文件:OpenHarmonyArkTSWebBrowser.cpp;修改函数:FArkTSOpenHarmonyWebBrowser::SetVisibility()。
引擎相对路径:
Engine/Source/Runtime/WebBrowser/Private/OpenHarmony/OpenHarmonyArkTSWebBrowser.cpp
定位 FArkTSOpenHarmonyWebBrowser::SetVisibility()。
修改后:
void FArkTSOpenHarmonyWebBrowser::SetVisibility(bool InIsVisible)
{
auto jsFunc = aki::JSBind::GetJSFunction("WebView.setVisible");
// 新增代码:绑定缺失时安全返回,避免空指针崩溃。
if (jsFunc == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("鸿蒙浏览器未注册 WebView.setVisible,无法更新可见性。"));
return;
}
jsFunc->Invoke<void>(WebViewTag, InIsVisible);
}
2
3
4
5
6
7
8
9
10
11
12
绑定缺失时记录错误并返回,避免解引用空指针。该错误日志仅在异常情况下输出。
# WebView.ts
文件:WebView.ts;修改位置:WebViewInfo 类声明、copyWebViewInfo() 辅助函数,以及 WebView 类中的 setWebViewRect()、setVisible()、setOpacityWebView()、setBackgroundTransparent()、setScalesPageToFit() 五个函数。
引擎相对路径:
Engine/Build/OpenHarmony/entry/src/main/ets/webview/WebView.ts
# WebViewInfo 类声明
增加 @Observed,以下仅展示修改后的类声明开头,类中原有字段和构造函数保持不变。
修改后:
// 新增代码:将浏览器属性变化同步到持有该对象的界面组件。
@Observed
export class WebViewInfo {
2
3
# copyWebViewInfo()
删除整个辅助函数。
修改后:
/* 删除代码:属性改为原地更新,不再需要复制浏览器状态对象。
function copyWebViewInfo(viewInfo: WebViewInfo): WebViewInfo {
let newViewInfo: WebViewInfo = new WebViewInfo(viewInfo.x, viewInfo.y, viewInfo.w, viewInfo.h, viewInfo.viewTag);
newViewInfo.url = viewInfo.url;
newViewInfo.isUrl = viewInfo.isUrl;
newViewInfo.zoomAccess = viewInfo.zoomAccess;
newViewInfo.visible = viewInfo.visible;
newViewInfo.controller = viewInfo.controller;
newViewInfo.opacity = viewInfo.opacity;
newViewInfo.backgroundColor = viewInfo.backgroundColor;
return newViewInfo;
}
*/
2
3
4
5
6
7
8
9
10
11
12
13
# WebView.setWebViewRect()
删除对象复制和数组项替换,直接更新原对象的区域属性。以下为修改后的完整函数。
修改后:
static setWebViewRect(viewTag: number, x: number, y: number, w: number, h: number): void {
let index = globalThis.webViewIndexMap.get(viewTag);
let tmpWebInfo: WebViewInfo = globalThis.webViewArray[index];
tmpWebInfo.x = px2vp(x);
tmpWebInfo.y = px2vp(y);
tmpWebInfo.w = px2vp(w);
tmpWebInfo.h = px2vp(h);
/* 删除代码:直接更新原对象,不再复制并替换数组项。
let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo);
globalThis.webViewArray[index] = newViewInfo;
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
# WebView.setVisible()
删除对象复制和数组项替换,直接更新原对象的可见性。以下为修改后的完整函数。
修改后:
static setVisible(viewTag: number, visible: boolean): void {
let index = globalThis.webViewIndexMap.get(viewTag);
if (globalThis.webViewArray[index].visible == visible) {
return;
}
let tmpWebInfo: WebViewInfo = globalThis.webViewArray[index];
tmpWebInfo.visible = visible;
/* 删除代码:直接更新原对象,不再复制并替换数组项。
let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo);
globalThis.webViewArray[index] = newViewInfo;
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
# WebView.setOpacityWebView()
删除对象复制和数组项替换,直接更新原对象的透明度。以下为修改后的完整函数。
修改后:
static setOpacityWebView(viewTag: number, opacity: number): void {
let index = globalThis.webViewIndexMap.get(viewTag);
if (globalThis.webViewArray[index].opacity == opacity) {
return;
}
let tmpWebInfo: WebViewInfo = globalThis.webViewArray[index];
tmpWebInfo.opacity = opacity;
/* 删除代码:直接更新原对象,不再复制并替换数组项。
let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo);
globalThis.webViewArray[index] = newViewInfo;
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
# WebView.setBackgroundTransparent()
删除对象复制和数组项替换,直接更新原对象的背景色。以下为修改后的完整函数。
修改后:
static setBackgroundTransparent(viewTag: number): void {
let index = globalThis.webViewIndexMap.get(viewTag);
if (globalThis.webViewArray[index].backgroundColor == Color.Transparent) {
return;
}
globalThis.webViewArray[index].backgroundColor = Color.Transparent;
/* 删除代码:直接更新原对象,不再复制并替换数组项。
let newViewInfo: WebViewInfo = copyWebViewInfo(globalThis.webViewArray[index]);
globalThis.webViewArray[index] = newViewInfo;
*/
}
2
3
4
5
6
7
8
9
10
11
12
# WebView.setScalesPageToFit()
删除对象复制和数组项替换,直接更新原对象的缩放设置。以下为修改后的完整函数。
修改后:
static setScalesPageToFit(viewTag: number, scalesPageToFit: boolean): void {
let index = globalThis.webViewIndexMap.get(viewTag);
if (globalThis.webViewArray[index].zoomAccess == scalesPageToFit) {
return;
}
let tmpWebInfo: WebViewInfo = globalThis.webViewArray[index];
tmpWebInfo.zoomAccess = scalesPageToFit;
/* 删除代码:直接更新原对象,不再复制并替换数组项。
let newViewInfo: WebViewInfo = copyWebViewInfo(tmpWebInfo);
globalThis.webViewArray[index] = newViewInfo;
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
这些方法直接更新同一个状态对象,不再通过复制对象刷新界面,也不重新创建浏览器控制器。
# UeWebview.ets
# UeWebView.viewInfo 属性声明
文件:UeWebview.ets;修改声明:UeWebView 组件的 viewInfo 属性。
引擎相对路径:
Engine/Build/OpenHarmony/entry/src/main/ets/webview/UeWebview.ets
定位 UeWebView 中的 viewInfo 声明。
修改后:
/* 删除代码:普通成员未建立浏览器状态同步关系。
viewInfo: WebViewInfo = new WebViewInfo(0, 0, 0, 0, 0);
*/
// 新增代码:同步父组件传入的浏览器状态。
@ObjectLink viewInfo: WebViewInfo;
2
3
4
5
6
不要保留本地初始化。该属性由父组件传入,与前一步的 @Observed 配合,使尺寸、可见性等属性更新到界面。
# Index.ets.template
# Index.build()
文件:Index.ets.template;修改函数:Index.build(),具体为渲染 webViewArray 的 ForEach。
引擎相对路径:
Engine/Build/OpenHarmony/entry/src/main/ets/pages/Index.ets.template
定位渲染 webViewArray 的 ForEach。以下为 Index.build() 中的局部代码,保留函数中的其他布局。
修改后:
ForEach(this.webViewArray, (item: WebViewInfo) => {
UeWebView({ viewInfo: item })
},
/* 删除代码:位置和尺寸变化不应改变组件标识。
(item: WebViewInfo) => (item.viewTag + item.x + item.y + item.w + item.h).toString()
*/
// 新增代码:仅以固定的 viewTag 标识组件。
(item: WebViewInfo) => item.viewTag.toString())
2
3
4
5
6
7
8
位置和尺寸不再参与组件标识,避免布局变化时销毁并重新创建浏览器组件。
# 修改生效与验证
修改引擎源码及模板后,通过 UE 重新生成鸿蒙工程并打包,其中 C++ 修改需要重新编译。不要仅修改 Intermediate/OpenHarmony 内的生成文件;下次生成工程会覆盖这些文件。如果直接使用已有的 DevEco 工程打包,需要先将上述模板和源文件修改同步到该工程。
新包安装后,验证应用启动、客服首次显示、反复显示隐藏以及浏览器区域尺寸变化,确认没有崩溃、黑屏或因布局变化触发的重建。显示隐藏时按本页控制交互说明调用对应接口。
本节代码不包含临时诊断日志,不需要添加组件创建、销毁、布局或可见性打印回调。
# 功能介绍
从 CSDK 2.23.0 开始,可通过本页 UE 接口接入客服功能,包括查询客服开关、获取当前角色的客服地址、加载客服界面,以及获取客服会话标识。
客服是否开启可在初始化成功后查询;获取角色客服地址及有效客服会话需要角色登录成功。客服未读数量的查询、清除和变化通知复用会话接口。
# 为什么必须使用 UCsdkWebBrowser
客服界面必须使用 SDK 提供的 UCsdkWebBrowser 加载。 该控件提供客服接入所需的加载、交互控制、输入和附件选择能力,并统一提供加载状态事件。
使用该控件可获得 SDK 支持的完整客服能力。加载客服地址应调用 CsdkLoadURL(),交互控制使用本页列出的公开接口。
| 项目 | 内容 |
|---|---|
| 客服业务接口类 | UCsdkCustomerServiceLibrary |
| 业务接口模块/头文件 | CsdkRuntime/CustomerService/CsdkCustomerServiceLibrary.h |
| 浏览器控件类 | UCsdkWebBrowser |
| 浏览器模块/头文件 | CsdkBrowser/Widget/WebBrowser/CsdkWebBrowser.h |
| 客服业务接口分类 | CSDK|CustomerService |
| 浏览器接口分类 | CSDK|WebBrowser |
C++ 调用方在自身模块的 Build.cs 中添加依赖:
PrivateDependencyModuleNames.AddRange(new[] { "CsdkRuntime", "CsdkBrowser" });
如果自身模块的公开头文件暴露了这些模块的类型,应按 Unreal 模块规则配置公开依赖。
# 客服业务接口
# 查询客服是否开启
static bool IsCustomerServiceEnabled();
初始化成功后调用,返回产品是否开启客服。未初始化、初始化失败、服务端未开启或 SDK 已销毁时返回 false。该接口不要求角色登录;返回 true 不代表角色已登录或客服界面已加载完成。
# 获取客服会话标识
static FCsdkSessionId GetCustomerServiceSessionId();
返回当前已登录角色的固定客服会话。SDK 未初始化、角色未登录或角色标识无效时,返回无效会话。
# 获取客服地址
static FString GetCustomerServiceURL();
无参数,返回根据当前角色构建完成的完整客服 URL,可直接交给 UCsdkWebBrowser::CsdkLoadURL();接入方无需自行补充登录参数。
要求 SDK 初始化成功、角色已登录、客服已开启、角色及区服标识有效,且客服地址配置有效。无法获取时返回空字符串,调用方应先判断非空。该函数只返回地址,不自动创建控件或加载客服界面。
地址对应当前角色。登出或换号后应清空旧客服界面,待新角色登录后重新获取并加载地址。不要将地址长期缓存或写入日志。
# 客服属于会话
客服使用现有会话体系,同一已登录角色对应一个固定客服会话:
| 字段 | 值 |
|---|---|
ReceiverId | "cs" |
ServerId | 当前角色的 ServerId |
Type | ECsdkSessionType::CustomerService |
应使用 GetCustomerServiceSessionId() 获取该标识。客服未读数量查询、清除及变化通知复用会话接口,具体用法请跳转:
客服未读返回的是数量,可用于数字角标或以是否大于零判断红点。当前版本的未读数量查询返回类型为 int64;会话文档中的旧示例若仍标为 int32,以当前 SDK 声明为准。
客服未读计入总未读,不要求客服出现在本地会话列表。清除由业务显式调用现有会话清除接口;SDK 不因客服界面加载完成或显示在前台而自动清除。绑定变化通知后应主动查询一次当前数量。
# 浏览器公开接口
# 加载地址
void CsdkLoadURL(const FString& URL);
| 参数 | 说明 |
|---|---|
URL | 待加载地址,客服场景传入 GetCustomerServiceURL() 返回的非空地址 |
加载指定地址,支持在控件创建前调用。传入空字符串加载 about:blank,可用于清空客服界面。每次调用都会发起加载,日常显示切换不应重复调用。
控件也支持创建时加载 InitialURL,为空时加载空白界面。编辑器设计预览不发起加载。
# 控制交互
void CsdkEnableBrowserInteraction();
void CsdkDisableBrowserInteraction();
2
默认禁止交互。启用后允许操作客服界面;禁用后停止接收交互,保留已加载内容。支持在控件创建前设置交互状态。
显示客服界面时调用 CsdkEnableBrowserInteraction(),隐藏时调用 CsdkDisableBrowserInteraction()。这两个接口不重新加载地址,也不修改外层 UMG 控件的可见性,控件显示隐藏由接入方管理。
# 加载及浏览器事件
| 事件 | 参数 | 说明 |
|---|---|---|
OnCsdkLoadStarted | const FString& Url | 开始加载 |
OnCsdkLoadCompleted | const FString& Url | 加载完成 |
OnCsdkLoadError | int32 ErrorCode, const FString& ErrorMessage, const FString& Url | 加载失败,返回错误信息 |
启用交互不代表加载完成,可通过加载事件更新界面的加载提示或错误提示。
# 控件使用说明
日常显示切换应保留同一个控件实例,避免通过销毁并重新创建控件实现切换。登出或换号时禁用交互并加载空白地址,随后按新角色重新获取并加载客服地址。
版本下载见修订说明。