# tencentqianwebview — HarmonyOS ArkTS SDK Module

腾讯电子签 H5 容器 SDK HarmonyOS NEXT 实现的核心模块，独立产出 `.har`，客户接入只需依赖此模块。

## 模块标识

| 项 | 值 |
|---|---|
| 模块名 | `tencentqianwebview` |
| 模块类型 | HAR Static Library |
| 语言 | ArkTS（严格模式） |
| API Level | 12+（HarmonyOS NEXT 5.0+） |
| 内核 | ArkWeb |
| 集成方式 | 源码集成(推荐)/ ohpm 依赖 |

## 目录结构

```
tencentqianwebview/
├── src/
│   └── main/
│       ├── ets/
│       │   ├── TencentQianWebView.ets              # 容器模式公共入口
│       │   ├── TencentQianWebViewConfigurator.ets  # 配置模式公共入口
│       │   ├── TencentQianWebViewPage.ets          # 容器内置承载页
│       │   ├── TencentQianWebViewComponent.ets     # 组件形态承载页（内建 RESIZE_CONTENT 键盘避让）
│       │   ├── TencentQianWebViewController.ets    # 嵌入模式 Web 控制器（后退/重载，供 Component 使用）
│       │   ├── TencentQianWebViewPrewarmer.ets     # 预热公共入口（configure/prewarm/releaseAll）
│       │   ├── filechooser/                        # 文件选择器 + Media Capture
│       │   ├── internal/                           # WebSettings / Cookie / UA / 下载 / 桥接 / RenderRecovery
│       │   ├── internal/prewarm/                  # 鸿蒙离屏池 / manifest / 去重 / 语言工具 / WebNode
│       │   ├── model/                              # Config / SignResult / SignCallback / SdkEvent / Prewarm*
│       │   ├── permission/                         # 运行时权限封装
│       │   └── scheme/                            # qianapp / 外部 scheme 拦截
│       ├── resources/                              # 字符串、图片、布局
│       └── module.json5                            # 模块元数据
├── Index.ets                                       # 对外导出入口
├── oh-package.json5                                # ohpm 配置
└── hvigorfile.ts                                   # hvigor 构建脚本
```

## 公共 API 速览

### 容器模式 `TencentQianWebView.open`

SDK 内置承载页，一次调用即可拉起签署 H5：

```typescript
import {
  TencentQianWebView,
  TencentQianWebViewConfig,
  SignResult,
} from 'tencentqianwebview';
import common from '@ohos.app.ability.common';

TencentQianWebView.open(
  context,                                  // common.UIAbilityContext
  signUrl,
  new TencentQianWebViewConfig(),
  (result: SignResult) => {
    // result.action / result.result / result.flowId / result.extras
  },
);
```

### 组件模式 `TencentQianWebViewComponent`

将签署 WebView 嵌入宿主自定义布局（tab 页签、BottomSheet 等），顶栏 / 进度条 / 错误页由宿主自绘。SDK 托管 WebView 与全部能力，宿主掌控布局与 UI：

```typescript
import {
  TencentQianWebViewComponent,
  TencentQianWebViewController,
  TencentQianWebViewConfig,
  SignResult,
} from 'tencentqianwebview';
import common from '@ohos.app.ability.common';

@Component
struct MySignPage {
  private controller = new TencentQianWebViewController();

  build() {
    Column() {
      TencentQianWebViewComponent({
        url: this.signUrl,
        context: getContext(this) as common.UIAbilityContext,
        config: new TencentQianWebViewConfig(),
        onSignResult: (r: SignResult) => { /* 处理结果 */ },
        onTitleChanged: (title: string, canGoBack: boolean) => { /* 更新自绘顶栏 */ },
        onProgressChanged: (progress: number, visible: boolean) => { /* 显示进度条 */ },
        onRenderUnrecoverable: () => { /* 显示错误页 + this.controller.reload() 重试 */ },
        controller: this.controller,
        reusePrewarmPool: false,  // 默认关闭
      })
    }
  }
}
```

> `aboutToDisappear` 仅暂停渲染，组件随 `@Component` 生命周期自动清理。完整参数面与关键约束详见 [../../docs/usage-guide.md §1.5](../../docs/usage-guide.md#15-组件模式嵌入自定义布局)。

### 配置模式 `TencentQianWebViewConfigurator.apply`

返回一组 handlers，客户把每个回调函数**逐个绑定**到自有 `Web` 组件对应的链式属性上。SDK 不替换、不重建客户的 `WebviewController`：

```typescript
import {
  TencentQianWebViewConfigurator,
  TencentQianWebViewConfig,
  TencentQianWebViewHandlers,
  SignResult,
} from 'tencentqianwebview';

@Entry
@Component
struct MyWebPage {
  private controller: webview.WebviewController = new webview.WebviewController();
  private handlers: TencentQianWebViewHandlers = TencentQianWebViewConfigurator.apply(
    this.controller,
    this.getUIContext().getHostContext() as common.UIAbilityContext,
    new TencentQianWebViewConfig(),
    (result: SignResult) => { /* ... */ },
  );

  build() {
    Web({ src: signUrl, controller: this.controller })
      .javaScriptAccess(true)
      .domStorageAccess(true)
      .fileAccess(false)
      .multiWindowAccess(false)
      // 软键盘避让（API 12+）：键盘弹出收缩 Web 区域，H5 fixed 底栏跟随键盘上方。
      // ArkWeb 默认值即 RESIZE_CONTENT，未覆盖则天然正确；建议显式绑定固化语义。
      // 容器 / 组件模式 SDK 已内建，配置模式按 checklist C.4 核对。
      .keyboardAvoidMode(WebKeyboardAvoidMode.RESIZE_CONTENT)
      .onControllerAttached(this.handlers.onControllerAttached)
      .onLoadIntercept(this.handlers.onLoadIntercept)
      .onPageBegin(this.handlers.onPageBegin)
      .onPageEnd(this.handlers.onPageEnd)
      .onProgressChange(this.handlers.onProgressChange)
      .onPermissionRequest(this.handlers.onPermissionRequest)
      .onShowFileSelector(this.handlers.onShowFileSelector)
      .onAlert(this.handlers.onAlert)
      .onConfirm(this.handlers.onConfirm)
      .onPrompt(this.handlers.onPrompt)
      .onDownloadStart(this.handlers.onDownloadStart)
      .onErrorReceive(this.handlers.onErrorReceive)
      .onHttpErrorReceive(this.handlers.onHttpErrorReceive)
      .onSslErrorEventReceive(this.handlers.onSslErrorEventReceive)
      .onRenderExited(this.handlers.onRenderExited)
      .javaScriptOnDocumentStart(this.handlers.documentStartScripts);
  }
}
```

> 配置模式比 Android / iOS 多了"逐回调绑定"这一步，是因为 ArkUI 的 `Web` 组件回调是**链式属性**而非 delegate 对象，必须由客户在 `build` 中显式声明。**漏绑任一回调都会导致对应能力失效**。

### 预热（可选，提升首屏速度）

预热与容器/配置模式**正交**，不预热时行为与现状完全一致，预热是纯增量收益。`configure()` 可省（首次 `prewarm` 会以默认 `PROD` 懒初始化）；`MINIMAL` 仅 DNS、收益小，真正提速靠 `LIGHT` / `HEAVY`，按意图触发即可：

```typescript
const cfg = new PrewarmConfig();
cfg.environment = PrewarmEnvironment.PROD;
TencentQianWebViewPrewarmer.configure(cfg);
TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.LIGHT, 'single-sign');

// ⚠️ HEAVY 需传 uiContext（离屏 Web 组件依赖 UIContext），只能在页面/组件内调用：
//    TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.HEAVY, 'single-sign', this.getUIContext());

// 签署流程结束、不再需要时主动释放（回收预热态 WebView 占用的内存，每实例约 200MB）
TencentQianWebViewPrewarmer.releaseAll();
```

> 鸿蒙 `MINIMAL`/`LIGHT` 不需 `uiContext`（可在 `EntryAbility.onCreate` 调）；仅 `HEAVY` 建离屏 Web 组件时必填，须在页面/组件内用 `this.getUIContext()` 取得。缺失时 `HEAVY` 静默降级（不建池、不影响 `open()`）。

完整档位语义、触发时机、池生命周期与端差异详见 [../../docs/usage-guide.md §3.5](../../docs/usage-guide.md#35-预热可选提升首屏速度)。

## 关键 Config 字段

| 字段 | 默认值 | 说明 |
|---|---|---|
| `topBarTitle` | `''` | 顶栏标题（页面形态生效） |
| `confirmBeforeClose` | `false` | 点关闭时是否弹「离开页面?」二次确认 |
| `confirmCloseTitle` | `''` | 二次确认弹窗标题，空走默认「离开页面?」 |
| `confirmCloseMessage` | `''` | 二次确认弹窗正文，空走默认 |
| `confirmLeaveText` | `''` | 二次确认「仍要离开」按钮文案，空走默认 |
| `confirmStayText` | `''` | 二次确认「留在本页」按钮文案，空走默认 |
| `injectUserAgent` | `true` | 是否注入 `qianwv/<version>` UA 标识 |
| `userAgentExtra` | `''` | 客户附加的 UA 段 |
| `externalSchemes` | `['weixin', 'alipays']` | 允许跳出的外部 scheme 白名单 |
| `allowMixedContent` | `false` | 是否允许 https 页面加载 http 子资源 |

完整字段见 `model/TencentQianWebViewConfig.ets`。

## 集成方式

### 源码集成(推荐)

将本模块以 `file` 依赖引入客户工程,SDK 源码与客户代码在同一编译单元内一起编译、一起混淆,
所有符号引用天然一致,**无需配置任何混淆规则**。

在客户工程(Entry 模块)的 `oh-package.json5` 中添加:

```json5
{
  "dependencies": {
    "tencentqianwebview": "file:../tencentqianwebview"
  }
}
```

## 单元测试

```bash
bash scripts/test-harmony.sh
```

## 集成与排错

- 接入指南：[../../docs/usage-guide.md](../../docs/usage-guide.md)
- 接入自检清单：[../../docs/integration-checklist.md](../../docs/integration-checklist.md)
- 排错决策树 + FAQ：[../../docs/troubleshooting.md](../../docs/troubleshooting.md)
- `qianapp://` 协议规范：[../../docs/qianapp-protocol.md](../../docs/qianapp-protocol.md)
